@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
@@ -2,13 +2,15 @@
2
2
  * Read-only projection of the Agent's `state.db` (Python `api/agent_sessions.py`):
3
3
  * importable agent session rows with compression-chain collapse, CLI visibility
4
4
  * rules, the O(1) change fingerprint used by the gateway watcher, and existence
5
- * probes. Uses `node:sqlite`; every read opens a short-lived read-only handle
6
- * with a bounded busy timeout, and no path here ever writes to the database.
5
+ * probes. Uses cached read-only `node:sqlite` handles with a bounded busy timeout
6
+ * and a fresh snapshot per read; no path here ever writes to the database.
7
7
  */
8
8
  import { existsSync, openSync, readSync, closeSync, statSync } from 'node:fs';
9
9
  import { DatabaseSync } from 'node:sqlite';
10
10
  import { createHash } from 'node:crypto';
11
11
  import { Worker } from 'node:worker_threads';
12
+ import { AsyncLocalStorage } from 'node:async_hooks';
13
+ import { startupTimingScope } from '../startup-timing.js';
12
14
  import { str } from '../util.js';
13
15
  import { stateDbMessageFingerprint, stateDbMessageKey } from './state-db-identity.js';
14
16
  import { MESSAGING_SOURCES } from './source-kind.js';
@@ -118,6 +120,8 @@ export function isCliSessionRowVisible(row) {
118
120
  // ── compression-chain projection ─────────────────────────────────────────
119
121
  /** The Agent's `_CHAIN_CAP`: the most hops a walk along a compression lineage takes. Its visited check stops a cycle sooner. */
120
122
  const COMPRESSION_CHAIN_CAP = 1000;
123
+ /** The most ids the Agent's `_resume_lineage_ids` returns, a tighter defensive bound than `_CHAIN_CAP`. */
124
+ const RESUME_LINEAGE_CAP = 100;
121
125
  function isContinuationSession(parent, child) {
122
126
  if (!parent || !child)
123
127
  return false;
@@ -138,6 +142,37 @@ function isContinuationSession(parent, child) {
138
142
  const ended = Number.parseFloat(str(endedAt));
139
143
  return Number.isFinite(started) && Number.isFinite(ended) && started + 1.0 >= ended;
140
144
  }
145
+ /**
146
+ * `ids` plus every session a compression continuation (`isContinuationSession`) joins to one of them, up and down, at
147
+ * most `COMPRESSION_CHAIN_CAP` in all: the rows `projectAgentSessionRows` needs to collapse their chains as the sidebar
148
+ * listing does. Forks and other children are never followed, so they cannot crowd a continuation out of the cap.
149
+ */
150
+ function compressionChainIds(db, cols, ids) {
151
+ if (!['parent_session_id', 'end_reason'].every((c) => cols.has(c)))
152
+ return ids;
153
+ const fields = ['id', 'source', 'session_source', 'model_config', 'started_at', 'parent_session_id', 'end_reason', 'ended_at'].map((c) => cols.has(c) ? c : `NULL AS ${c}`).join(', ');
154
+ const row = db.prepare(`SELECT ${fields} FROM sessions WHERE id = ?`);
155
+ const children = db.prepare(`SELECT ${fields} FROM sessions WHERE parent_session_id = ?`);
156
+ const seen = new Set(ids);
157
+ const pending = [...ids];
158
+ const visit = (next) => { if (next && !seen.has(next) && seen.size < COMPRESSION_CHAIN_CAP) {
159
+ seen.add(next);
160
+ pending.push(next);
161
+ } };
162
+ while (pending.length) {
163
+ const current = row.get(pending.pop());
164
+ if (!current)
165
+ continue;
166
+ const parentId = str(current.parent_session_id);
167
+ if (parentId && isContinuationSession(row.get(parentId), current))
168
+ visit(parentId);
169
+ if (['compression', 'cli_close'].includes(str(current.end_reason)))
170
+ for (const child of children.all(str(current.id)))
171
+ if (isContinuationSession(current, child))
172
+ visit(str(child.id));
173
+ }
174
+ return [...seen];
175
+ }
141
176
  function continuationRootId(rowsById, sessionId) {
142
177
  if (!sessionId)
143
178
  return null;
@@ -159,7 +194,7 @@ function continuationRootId(rowsById, sessionId) {
159
194
  return rootId;
160
195
  }
161
196
  /** Python `_project_agent_session_rows`: collapse compression chains into one sidebar row pointing at the freshest importable tip. */
162
- export function projectAgentSessionRows(rows) {
197
+ export function projectAgentSessionRows(rows, keepEmpty = false) {
163
198
  const rowsById = new Map(rows.map((r) => [str(r.id), r]));
164
199
  const childrenByParent = new Map();
165
200
  const continuationChildIds = new Set();
@@ -225,8 +260,12 @@ export function projectAgentSessionRows(rows) {
225
260
  let tip = row;
226
261
  if (['compression', 'cli_close'].includes(str(row.end_reason)))
227
262
  [tip, segments] = compressionTip(row);
228
- if (!tip || positiveInt(tip.actual_message_count) <= 0)
263
+ // `keepEmpty` (a direct lookup of receipt-proven Web diagnostic chats, TAL-1040) keeps a turnless plain row.
264
+ if (!tip || positiveInt(tip.actual_message_count) <= 0) {
265
+ if (keepEmpty && tip === row)
266
+ projected.push({ ...row });
229
267
  continue;
268
+ }
230
269
  if (tip === row) {
231
270
  projected.push({ ...row });
232
271
  continue;
@@ -255,17 +294,96 @@ export function projectAgentSessionRows(rows) {
255
294
  projected.sort((a, b) => score(b.last_activity, b.started_at) - score(a.last_activity, a.started_at));
256
295
  return projected;
257
296
  }
258
- // ── sqlite access ─────────────────────────────────────────────────────────
259
- /** Python `open_state_db_readonly`: read-only URI open with a bounded busy timeout; the caller closes it. */
260
- export function openStateDbReadonly(dbPath, busyTimeoutMs = STATE_DB_BUSY_TIMEOUT_MS) {
261
- if (!existsSync(dbPath))
262
- throw new Error(`agent state.db not found: ${dbPath}`);
263
- const db = new DatabaseSync(dbPath, { readOnly: true, timeout: busyTimeoutMs });
264
- return db;
297
+ /** Bound descriptors and page caches even when deleted profiles are never read again. */
298
+ const STATE_DB_HANDLE_CACHE_MAX = 16;
299
+ const readonlyStateDbs = new Map();
300
+ const schemaCaches = new WeakMap();
301
+ /** Cache ownership ends at shutdown; worker termination also disposes its native SQLite resources. */
302
+ function closeStateDbs(paths = readonlyStateDbs.keys()) {
303
+ for (const path of paths) {
304
+ const cached = readonlyStateDbs.get(path);
305
+ if (cached?.db.isOpen)
306
+ cached.db.close();
307
+ readonlyStateDbs.delete(path);
308
+ }
309
+ }
310
+ process.once('exit', () => { closeStateDbs(); });
311
+ /** One handle per file identity. The caller ends the read snapshot in finally, leaving closure to the cache. */
312
+ function openStateDbReadonly(dbPath, busyTimeoutMs = STATE_DB_BUSY_TIMEOUT_MS) {
313
+ const { ino, dev, mtimeNs } = statSync(dbPath, { bigint: true });
314
+ let cached = readonlyStateDbs.get(dbPath);
315
+ // Raw writes need not advance SQLite's change counter; revalidate the main file after any such change.
316
+ if (cached && (cached.ino !== ino || cached.dev !== dev || cached.mtimeNs !== mtimeNs || !cached.db.isOpen)) {
317
+ closeStateDbs([dbPath]);
318
+ cached = undefined;
319
+ }
320
+ if (!cached) {
321
+ if (readonlyStateDbs.size >= STATE_DB_HANDLE_CACHE_MAX)
322
+ closeStateDbs([readonlyStateDbs.keys().next().value]);
323
+ const db = new DatabaseSync(dbPath, { readOnly: true, timeout: busyTimeoutMs });
324
+ cached = { db, ino, dev, mtimeNs, schemaVersion: null, columns: new Map(), indexes: new Map() };
325
+ schemaCaches.set(db, cached);
326
+ }
327
+ readonlyStateDbs.delete(dbPath);
328
+ readonlyStateDbs.set(dbPath, cached);
329
+ const { db } = cached;
330
+ try {
331
+ db.exec(`PRAGMA busy_timeout = ${Math.max(0, Math.trunc(busyTimeoutMs))}`);
332
+ // The schema cookie and all probes/queries must describe the same committed snapshot.
333
+ db.exec('BEGIN');
334
+ const version = db.prepare('PRAGMA schema_version').get().schema_version;
335
+ if (cached.schemaVersion !== version) {
336
+ cached.columns.clear();
337
+ cached.indexes.clear();
338
+ cached.schemaVersion = version;
339
+ }
340
+ return db;
341
+ }
342
+ catch (error) {
343
+ closeStateDbs([dbPath]);
344
+ throw error;
345
+ }
265
346
  }
266
- /** The table's columns; a failed read throws, so an unreadable database is never mistaken for an older schema (TAL-785). */
347
+ function finishStateDbRead(db) {
348
+ try {
349
+ db.exec('COMMIT');
350
+ }
351
+ catch {
352
+ // A failed read can poison COMMIT too; discard the handle without hiding the reader's failure result.
353
+ db.close();
354
+ }
355
+ }
356
+ /** A failed probe throws, so unreadable storage is never mistaken for an older schema (TAL-785). */
267
357
  function tableColumns(db, table) {
268
- return new Set(db.prepare(`PRAGMA table_info(${table})`).all().map((r) => r.name));
358
+ const cache = schemaCaches.get(db).columns;
359
+ let columns = cache.get(table);
360
+ if (!columns) {
361
+ columns = new Set(db.prepare(`PRAGMA table_info(${table})`).all().map((r) => r.name));
362
+ cache.set(table, columns);
363
+ }
364
+ return columns;
365
+ }
366
+ function tableIndexes(db, table) {
367
+ const cache = schemaCaches.get(db).indexes;
368
+ const indexes = cache.get(table);
369
+ if (indexes)
370
+ return indexes;
371
+ try {
372
+ const found = new Set(db.prepare(`PRAGMA index_list(${table})`).all().map((r) => r.name));
373
+ cache.set(table, found);
374
+ return found;
375
+ }
376
+ catch {
377
+ return new Set();
378
+ }
379
+ }
380
+ /** Agent `_sql_freshest_of`: the `last_activity_at` heartbeat can lag a newer message, so it never ranks alone. */
381
+ function freshestActivity(lastActivityAt, lastMessage) {
382
+ return `MAX(COALESCE(${lastActivityAt}, 0), COALESCE(${lastMessage}, 0), COALESCE(s.started_at, 0))`;
383
+ }
384
+ /** Stale Agent statistics can favor a non-covering index and read every message row. */
385
+ function lastMessageTimestamp(indexed, alias = 'm') {
386
+ return `(SELECT MAX(${alias}.timestamp) FROM messages ${alias}${indexed ? ' INDEXED BY idx_messages_session' : ''} WHERE ${alias}.session_id = s.id)`;
269
387
  }
270
388
  const LOCAL_POLICY_PREFIX = 'gateway.local_policy.v1:';
271
389
  /** Read-only equivalent of Agent validate_local_lineage. A receipt, never a reset edge alone, proves ownership. */
@@ -279,8 +397,8 @@ function localLineages(db, sid) {
279
397
  const meta = db.prepare('SELECT value FROM state_meta WHERE key = ?');
280
398
  const fetch = db.prepare('SELECT * FROM sessions WHERE id IN (SELECT value FROM json_each(?))');
281
399
  const messageCols = tableColumns(db, 'messages');
282
- const lastMessage = ['session_id', 'timestamp'].every((c) => messageCols.has(c)) ? '(SELECT MAX(m.timestamp) FROM messages m WHERE m.session_id = s.id)' : 'NULL';
283
- const activity = `MAX(COALESCE(${cols.has('last_activity_at') ? 's.last_activity_at' : 'NULL'}, 0), COALESCE(${lastMessage}, 0), COALESCE(s.started_at, 0))`;
400
+ const lastMessage = ['session_id', 'timestamp'].every((c) => messageCols.has(c)) ? lastMessageTimestamp(tableIndexes(db, 'messages').has('idx_messages_session')) : 'NULL';
401
+ const activity = freshestActivity(cols.has('last_activity_at') ? 's.last_activity_at' : 'NULL', lastMessage);
284
402
  // Agent _CHAIN_STEP_SQL prefers a compression child, then a live child, then recency and id.
285
403
  const children = db.prepare(`SELECT s.* FROM sessions s WHERE s.parent_session_id = ? ORDER BY CASE WHEN s.end_reason = 'compression' THEN 0 WHEN s.ended_at IS NULL THEN 1 ELSE 2 END, ${activity} DESC, s.started_at DESC, s.id DESC`);
286
404
  const explicitChild = (child, includeReset) => {
@@ -373,7 +491,6 @@ export function stateDbLocalTarget(dbPath, sid, opts = {}) {
373
491
  return sid;
374
492
  }
375
493
  try {
376
- db.exec('BEGIN');
377
494
  const target = localLineages(db, sid).get(sid)?.ids.at(-1) ?? sid;
378
495
  opts.onRead?.(target);
379
496
  return target;
@@ -382,9 +499,15 @@ export function stateDbLocalTarget(dbPath, sid, opts = {}) {
382
499
  return sid;
383
500
  }
384
501
  finally {
385
- db.close();
502
+ finishStateDbRead(db);
386
503
  }
387
504
  }
505
+ /** `stateDbLocalTarget` plus whether state.db answered (a missing one does), so only a real answer seeds a routing cache. */
506
+ function stateDbLocalTargetRead(dbPath, sid) {
507
+ let read = false;
508
+ const target = stateDbLocalTarget(dbPath, sid, { onRead: () => { read = true; } });
509
+ return { target, read };
510
+ }
388
511
  export const AGENT_STORAGE_OK = Object.freeze({ state: 'ok', reason: null });
389
512
  /**
390
513
  * The corrupt-storage reason when `error` is SQLite's `SQLITE_CORRUPT` or `SQLITE_NOTADB` (the Agent's
@@ -404,6 +527,9 @@ export function stateDbReadFailure(error) {
404
527
  return { state: 'corrupt', reason: corrupt };
405
528
  return { state: 'unavailable', reason: `Agent session storage (state.db) could not be read: ${error instanceof Error ? error.message : String(error)}.` };
406
529
  }
530
+ function outdatedStateDbStorage() {
531
+ return { state: 'outdated', reason: "state.db has no 'source' column (older hermes-agent?). Agent sessions unavailable. Upgrade hermes-agent to fix this." };
532
+ }
407
533
  /**
408
534
  * TAL-785: whether `state.db` can serve the session list: `corrupt` when SQLite reports the file damaged, `unavailable`
409
535
  * when it cannot be read for another reason (permissions, a lock, an I/O error), `outdated` when a readable database
@@ -417,10 +543,10 @@ export function stateDbStorage(dbPath) {
417
543
  const db = openStateDbReadonly(dbPath);
418
544
  try {
419
545
  if (!tableColumns(db, 'sessions').has('source'))
420
- return { state: 'outdated', reason: `state.db has no 'source' column (older hermes-agent?). Agent sessions unavailable. Upgrade hermes-agent to fix this.` };
546
+ return outdatedStateDbStorage();
421
547
  }
422
548
  finally {
423
- db.close();
549
+ finishStateDbRead(db);
424
550
  }
425
551
  }
426
552
  catch (error) {
@@ -441,45 +567,42 @@ export function setStateDbReadDelayForTests(ms) { readDelayMs = ms; }
441
567
  * `idx_messages_session` exactly as Python does when its heal fails.
442
568
  */
443
569
  export function readImportableAgentSessionRows(dbPath, opts = {}) {
570
+ const result = readImportableAgentSessionRowBatches(dbPath, [opts]).rows[0];
571
+ if (result instanceof Error)
572
+ throw result;
573
+ return result;
574
+ }
575
+ /** All listing windows share one read snapshot, schema, validated local lineages and ownership lookup. */
576
+ export function readImportableAgentSessionRowBatches(dbPath, queries) {
444
577
  if (!existsSync(dbPath))
445
- return [];
446
- const limit = opts.limit === undefined ? 200 : opts.limit;
447
- const exclude = opts.excludeSources === undefined ? ['cron', 'webui'] : opts.excludeSources;
448
- const include = opts.includeSources ?? null;
578
+ return { rows: queries.map(() => []), storage: AGENT_STORAGE_OK };
449
579
  if (readDelayMs > 0)
450
580
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, readDelayMs);
451
581
  const db = openStateDbReadonly(dbPath);
452
582
  try {
453
- db.exec('BEGIN'); // Receipt and physical rows must belong to the same committed generation.
454
- const local = localLineages(db, opts.sessionIds?.length === 1 ? opts.sessionIds[0] : undefined);
455
583
  const sessionCols = tableColumns(db, 'sessions');
456
584
  const messageCols = tableColumns(db, 'messages');
457
585
  if (!sessionCols.has('source')) {
458
586
  if (!warnedNoSourceColumn.has(dbPath)) {
459
587
  warnedNoSourceColumn.add(dbPath);
460
- opts.log?.(`[webui] agent session listing skipped: state.db at ${dbPath} has no 'source' column (older hermes-agent?). Agent sessions unavailable. Upgrade hermes-agent to fix this.`);
588
+ queries[0]?.log?.(`[webui] agent session listing skipped: state.db at ${dbPath} has no 'source' column (older hermes-agent?). Agent sessions unavailable. Upgrade hermes-agent to fix this.`);
461
589
  }
462
- return [];
590
+ return { rows: queries.map(() => []), storage: outdatedStateDbStorage() };
463
591
  }
592
+ const only = queries.length === 1 ? queries[0]?.sessionIds : undefined;
593
+ const local = localLineages(db, only?.length === 1 ? only[0] : undefined);
464
594
  const col = (name) => (sessionCols.has(name) ? `s.${name}` : `NULL AS ${name}`);
465
595
  const messagesHasSessionId = messageCols.has('session_id');
466
596
  const messagesHasTimestamp = messageCols.has('timestamp');
467
597
  const countCol = messageCols.has('id') ? 'id' : 'session_id';
468
- let messagesIndexPresent = false;
469
- let userMessagesIndexPresent = false;
470
- if (messagesHasSessionId && messagesHasTimestamp) {
471
- try {
472
- const indexes = new Set(db.prepare('PRAGMA index_list(messages)').all().map((r) => r.name));
473
- messagesIndexPresent = indexes.has('idx_messages_session');
474
- userMessagesIndexPresent = indexes.has('idx_messages_session_user');
475
- }
476
- catch {
477
- messagesIndexPresent = false;
478
- }
479
- }
598
+ const messageIndexes = messagesHasSessionId && messagesHasTimestamp ? tableIndexes(db, 'messages') : new Set();
599
+ const sessionIndexes = tableIndexes(db, 'sessions');
600
+ const hasPolicies = Boolean(db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'state_meta'").get());
601
+ const messagesIndexPresent = messageIndexes.has('idx_messages_session');
602
+ const userMessagesIndexPresent = messageIndexes.has('idx_messages_session_user');
480
603
  let actualCountExpr;
481
604
  let userCountExpr;
482
- let lastActivityExpr;
605
+ let baseLastActivityExpr;
483
606
  let joinClause = '';
484
607
  let groupByClause = '';
485
608
  if (messagesHasSessionId) {
@@ -492,12 +615,12 @@ export function readImportableAgentSessionRows(dbPath, opts = {}) {
492
615
  }
493
616
  else
494
617
  userCountExpr = actualCountExpr;
495
- lastActivityExpr = messagesHasTimestamp ? '(SELECT MAX(lm.timestamp) FROM messages lm WHERE lm.session_id = s.id)' : 'NULL';
618
+ baseLastActivityExpr = messagesHasTimestamp ? lastMessageTimestamp(messagesIndexPresent, 'lm') : 'NULL';
496
619
  }
497
620
  else {
498
621
  actualCountExpr = `COUNT(m.${countCol})`;
499
622
  userCountExpr = messageCols.has('role') ? "EXISTS(SELECT 1 FROM messages um WHERE um.session_id = s.id AND LOWER(um.role) = 'user')" : `COUNT(m.${countCol})`;
500
- lastActivityExpr = messagesHasTimestamp ? 'MAX(m.timestamp)' : 'NULL';
623
+ baseLastActivityExpr = messagesHasTimestamp ? 'MAX(m.timestamp)' : 'NULL';
501
624
  joinClause = 'LEFT JOIN messages m ON m.session_id = s.id';
502
625
  groupByClause = 'GROUP BY s.id';
503
626
  }
@@ -505,100 +628,151 @@ export function readImportableAgentSessionRows(dbPath, opts = {}) {
505
628
  else {
506
629
  actualCountExpr = 's.message_count';
507
630
  userCountExpr = 's.message_count';
508
- lastActivityExpr = 'NULL';
509
- }
510
- let orderBy = 'ORDER BY s.started_at DESC';
511
- let latestMessagesCte = null;
512
- let candidateOrder = 'ORDER BY s.started_at DESC';
513
- const where = ['s.source IS NOT NULL'];
514
- const params = [];
515
- if (opts.sessionIds !== undefined) {
516
- where.push('s.id IN (SELECT value FROM json_each(?))');
517
- params.push(JSON.stringify(opts.sessionIds.map((id) => local.get(id)?.ids.at(-1) ?? id)));
518
- }
519
- const superseded = [...local].filter(([id, lineage]) => id !== lineage.ids.at(-1)).map(([id]) => id);
520
- if (superseded.length) {
521
- where.push('s.id NOT IN (SELECT value FROM json_each(?))');
522
- params.push(JSON.stringify(superseded));
631
+ baseLastActivityExpr = 'NULL';
523
632
  }
524
- const hasPolicies = Boolean(db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'state_meta'").get());
525
- const policySource = (id) => `(SELECT CASE WHEN json_valid(value) THEN json_extract(value, '$.policy.source') END FROM state_meta WHERE key = '${LOCAL_POLICY_PREFIX}' || ${id})`;
526
- const sourceSql = hasPolicies ? `COALESCE(${policySource('s.id')}, ${sessionCols.has('chat_id') ? policySource('s.chat_id') : 'NULL'}, s.source)` : 's.source';
527
- const included = (include ?? []).map(String).filter(Boolean);
528
- if (included.length) {
529
- where.push(`${sourceSql} IN (${included.map(() => '?').join(', ')})`);
530
- params.push(...included);
531
- }
532
- const excluded = (exclude ?? []).map(String).filter(Boolean);
533
- if (excluded.length) {
534
- where.push(`${sourceSql} NOT IN (${excluded.map(() => '?').join(', ')})`);
535
- params.push(...excluded);
536
- }
537
- const preaggregated = messagesHasSessionId && messagesHasTimestamp && included.length === 1 && included[0] === 'cron' && !messagesIndexPresent;
538
- if (preaggregated) {
539
- orderBy = 'ORDER BY COALESCE(MAX(m.timestamp), s.started_at) DESC';
540
- latestMessagesCte = 'latest_messages AS (SELECT mx.session_id AS session_id, MAX(mx.timestamp) AS last_message_at FROM messages mx GROUP BY mx.session_id)';
541
- candidateOrder = 'ORDER BY COALESCE(lm.last_message_at, s.started_at) DESC, s.started_at DESC';
542
- }
543
- else if (messagesHasSessionId && messagesHasTimestamp) {
544
- orderBy = messagesIndexPresent ? 'ORDER BY COALESCE(last_activity, s.started_at) DESC' : 'ORDER BY COALESCE(MAX(m.timestamp), s.started_at) DESC';
545
- candidateOrder = 'ORDER BY COALESCE((SELECT MAX(mx.timestamp) FROM messages mx WHERE mx.session_id = s.id), s.started_at) DESC, s.started_at DESC';
546
- }
547
- const selectSql = `SELECT s.id, s.title, s.model, s.message_count, s.started_at, s.source, ${['model_config', 'session_source', 'user_id', 'chat_id', 'chat_type', 'thread_id', 'session_key', 'origin_chat_id', 'origin_user_id', 'platform', 'parent_session_id', 'ended_at', 'end_reason'].map(col).join(', ')}, ${actualCountExpr} AS actual_message_count, ${userCountExpr} AS actual_user_message_count, ${lastActivityExpr} AS last_activity`;
548
- let raw;
549
- if (limit !== null) {
550
- const resultLimit = Math.max(0, Math.trunc(limit));
551
- if (resultLimit === 0)
552
- return [];
553
- const candidateLimit = Math.max(resultLimit * 8, resultLimit);
554
- const candidateCte = latestMessagesCte
555
- ? `WITH ${latestMessagesCte}, candidates AS (SELECT s.id FROM sessions s LEFT JOIN latest_messages lm ON lm.session_id = s.id WHERE ${where.join(' AND ')} ${candidateOrder} LIMIT ?)`
556
- : `WITH candidates AS (SELECT s.id FROM sessions s WHERE ${where.join(' AND ')} ${candidateOrder} LIMIT ?)`;
557
- raw = db.prepare(`${candidateCte} ${selectSql} FROM sessions s JOIN candidates c ON c.id = s.id ${joinClause} ${groupByClause} ${orderBy}`).all(...params, candidateLimit);
633
+ const candidates = (opts) => {
634
+ const limit = opts.limit === undefined ? 200 : opts.limit;
635
+ const exclude = opts.excludeSources === undefined ? ['cron', 'webui'] : opts.excludeSources;
636
+ const include = opts.includeSources ?? null;
637
+ let lastActivityExpr = baseLastActivityExpr;
638
+ let orderBy = 'ORDER BY s.started_at DESC';
639
+ let latestMessagesCte = null;
640
+ let candidateOrder = 'ORDER BY s.started_at DESC';
641
+ const where = ['s.source IS NOT NULL'];
642
+ const params = [];
643
+ if (opts.sessionIds !== undefined) {
644
+ where.push('s.id IN (SELECT value FROM json_each(?))');
645
+ const ids = opts.sessionIds.map((id) => local.get(id)?.ids.at(-1) ?? id);
646
+ params.push(JSON.stringify(opts.compressionChains ? compressionChainIds(db, sessionCols, ids) : ids));
647
+ }
648
+ const superseded = [...local].filter(([id, lineage]) => id !== lineage.ids.at(-1)).map(([id]) => id);
649
+ if (superseded.length) {
650
+ where.push('s.id NOT IN (SELECT value FROM json_each(?))');
651
+ params.push(JSON.stringify(superseded));
652
+ }
653
+ const policySource = (id) => `(SELECT CASE WHEN json_valid(value) THEN json_extract(value, '$.policy.source') END FROM state_meta WHERE key = '${LOCAL_POLICY_PREFIX}' || ${id})`;
654
+ const sourceSql = hasPolicies ? `COALESCE(${policySource('s.id')}, ${sessionCols.has('chat_id') ? policySource('s.chat_id') : 'NULL'}, s.source)` : 's.source';
655
+ const included = (include ?? []).map(String).filter(Boolean);
656
+ if (included.length) {
657
+ where.push(`${sourceSql} IN (${included.map(() => '?').join(', ')})`);
658
+ params.push(...included);
659
+ }
660
+ const excluded = (exclude ?? []).map(String).filter(Boolean);
661
+ if (excluded.length) {
662
+ where.push(`${sourceSql} NOT IN (${excluded.map(() => '?').join(', ')})`);
663
+ params.push(...excluded);
664
+ }
665
+ const preaggregated = messagesHasSessionId && messagesHasTimestamp && included.length === 1 && included[0] === 'cron' && !messagesIndexPresent;
666
+ // TAL-1163: the Agent's activity index preselects the window; exact recency is computed for those candidates only.
667
+ const activityIndexed = limit !== null && messagesHasSessionId && messagesHasTimestamp && sessionCols.has('last_activity_at')
668
+ && sessionIndexes.has('idx_sessions_effective_activity');
669
+ if (activityIndexed) {
670
+ lastActivityExpr = freshestActivity('s.last_activity_at', lastActivityExpr);
671
+ orderBy = 'ORDER BY last_activity DESC';
672
+ candidateOrder = 'ORDER BY COALESCE(s.last_activity_at, s.started_at) DESC, s.started_at DESC, s.id DESC';
673
+ }
674
+ else if (preaggregated) {
675
+ orderBy = 'ORDER BY COALESCE(MAX(m.timestamp), s.started_at) DESC';
676
+ latestMessagesCte = 'latest_messages AS (SELECT mx.session_id AS session_id, MAX(mx.timestamp) AS last_message_at FROM messages mx GROUP BY mx.session_id)';
677
+ candidateOrder = 'ORDER BY COALESCE(lm.last_message_at, s.started_at) DESC, s.started_at DESC';
678
+ }
679
+ else if (messagesHasSessionId && messagesHasTimestamp) {
680
+ orderBy = messagesIndexPresent ? 'ORDER BY COALESCE(last_activity, s.started_at) DESC' : 'ORDER BY COALESCE(MAX(m.timestamp), s.started_at) DESC';
681
+ candidateOrder = `ORDER BY COALESCE(${lastMessageTimestamp(messagesIndexPresent, 'mx')}, s.started_at) DESC, s.started_at DESC`;
682
+ }
683
+ const selectSql = `SELECT s.id, s.title, s.model, s.message_count, s.started_at, s.source, ${['model_config', 'session_source', 'user_id', 'chat_id', 'chat_type', 'thread_id', 'session_key', 'origin_chat_id', 'origin_user_id', 'platform', 'parent_session_id', 'ended_at', 'end_reason'].map(col).join(', ')}, ${actualCountExpr} AS actual_message_count, ${userCountExpr} AS actual_user_message_count, ${lastActivityExpr} AS last_activity`;
684
+ let raw;
685
+ if (limit !== null) {
686
+ const resultLimit = Math.max(0, Math.trunc(limit));
687
+ if (resultLimit === 0)
688
+ return [];
689
+ const candidateLimit = Math.max(resultLimit * 8, resultLimit);
690
+ const candidateCte = latestMessagesCte
691
+ ? `WITH ${latestMessagesCte}, candidates AS (SELECT s.id FROM sessions s LEFT JOIN latest_messages lm ON lm.session_id = s.id WHERE ${where.join(' AND ')} ${candidateOrder} LIMIT ?)`
692
+ : `WITH candidates AS (SELECT s.id FROM sessions s WHERE ${where.join(' AND ')} ${candidateOrder} LIMIT ?)`;
693
+ raw = db.prepare(`${candidateCte} ${selectSql} FROM sessions s JOIN candidates c ON c.id = s.id ${joinClause} ${groupByClause} ${orderBy}`).all(...params, candidateLimit);
694
+ }
695
+ else {
696
+ raw = db.prepare(`${selectSql} FROM sessions s ${joinClause} WHERE ${where.join(' AND ')} ${groupByClause} ${orderBy}`).all(...params);
697
+ }
698
+ return raw;
699
+ };
700
+ const batches = queries.map((opts) => {
701
+ try {
702
+ return candidates(opts);
703
+ }
704
+ catch (error) {
705
+ return error;
706
+ }
707
+ });
708
+ const ids = [...new Set(batches.flatMap((rows) => rows instanceof Error ? [] : rows.map((r) => str(r.id))))];
709
+ let owners;
710
+ try {
711
+ owners = sessionSources(db, sessionCols, ids);
558
712
  }
559
- else {
560
- raw = db.prepare(`${selectSql} FROM sessions s ${joinClause} WHERE ${where.join(' AND ')} ${groupByClause} ${orderBy}`).all(...params);
713
+ catch (error) {
714
+ if (queries.length === 1)
715
+ throw error;
716
+ // Retry ownership per window only on damage, so a background failure cannot hide healthy interactive rows.
561
717
  }
562
- const owners = sessionSources(db, sessionCols, raw.map((r) => str(r.id)));
563
- raw = raw.map((r) => { const owner = owners.get(str(r.id)); return { ...r, source: typeof owner === 'object' ? owner.source : owner || r.source }; });
564
- let projected = projectAgentSessionRows(raw.map((r) => {
565
- const lineage = local.get(str(r.id));
566
- if (!lineage)
567
- return { ...r };
568
- const root = lineage.rows[0];
569
- const title = lineage.title || r.title;
570
- return { ...r, title, started_at: root.started_at, last_activity: r.last_activity ?? r.started_at, parent_session_id: root.parent_session_id,
571
- _lineage_root_id: lineage.owner, _lineage_tip_id: r.id, _lineage_ids: [...lineage.ids], _compression_segment_count: lineage.ids.length };
572
- }));
573
- projected = projected.map((r) => {
574
- const parent = local.get(str(r.parent_session_id));
575
- return { ...r, ...normalizeAgentSessionSource(r.source), ...(parent ? { _parent_lineage_root_id: parent.owner, parent_title: parent.title, parent_source: parent.rows.at(-1)?.source ?? null } : {}) };
718
+ const rows = batches.map((batch, i) => {
719
+ if (batch instanceof Error)
720
+ return batch;
721
+ let batchOwners = owners;
722
+ if (batchOwners === undefined) {
723
+ try {
724
+ batchOwners = sessionSources(db, sessionCols, batch.map((row) => str(row.id)));
725
+ }
726
+ catch (error) {
727
+ return error;
728
+ }
729
+ }
730
+ let raw = batch;
731
+ const opts = queries[i];
732
+ const limit = opts.limit === undefined ? 200 : opts.limit;
733
+ raw = raw.map((r) => { const owner = batchOwners.get(str(r.id)); return { ...r, source: typeof owner === 'object' ? owner.source : owner || r.source }; });
734
+ const keepEmpty = opts.keepEmpty === true && opts.sessionIds !== undefined;
735
+ let projected = projectAgentSessionRows(raw.map((r) => {
736
+ const lineage = local.get(str(r.id));
737
+ if (!lineage)
738
+ return { ...r };
739
+ const root = lineage.rows[0];
740
+ const title = lineage.title || r.title;
741
+ return { ...r, title, started_at: root.started_at, last_activity: r.last_activity ?? r.started_at, parent_session_id: root.parent_session_id,
742
+ _lineage_root_id: lineage.owner, _lineage_tip_id: r.id, _lineage_ids: [...lineage.ids], _compression_segment_count: lineage.ids.length };
743
+ }), keepEmpty);
744
+ projected = projected.map((r) => {
745
+ const parent = local.get(str(r.parent_session_id));
746
+ return { ...r, ...normalizeAgentSessionSource(r.source), ...(parent ? { _parent_lineage_root_id: parent.owner, parent_title: parent.title, parent_source: parent.rows.at(-1)?.source ?? null } : {}) };
747
+ });
748
+ if (!keepEmpty)
749
+ projected = projected.filter(isCliSessionRowVisible);
750
+ if (limit === null)
751
+ return projected;
752
+ const selected = projected.slice(0, Math.max(0, Math.trunc(limit)));
753
+ const have = new Set(selected.map((r) => str(r.id)));
754
+ const byId = new Map(projected.filter((r) => r.id).map((r) => [str(r.id), r]));
755
+ const pending = [...selected];
756
+ while (pending.length) {
757
+ const row = pending.pop();
758
+ if (lower(row.raw_source || row.source) !== 'subagent')
759
+ continue;
760
+ const parentId = str(row.parent_session_id);
761
+ if (!parentId || have.has(parentId))
762
+ continue;
763
+ const parent = byId.get(parentId);
764
+ if (!parent || lower(parent.raw_source || parent.source) !== 'subagent')
765
+ continue;
766
+ selected.push(parent);
767
+ have.add(parentId);
768
+ pending.push(parent);
769
+ }
770
+ return selected;
576
771
  });
577
- projected = projected.filter(isCliSessionRowVisible);
578
- if (limit === null)
579
- return projected;
580
- const selected = projected.slice(0, Math.max(0, Math.trunc(limit)));
581
- const have = new Set(selected.map((r) => str(r.id)));
582
- const byId = new Map(projected.filter((r) => r.id).map((r) => [str(r.id), r]));
583
- const pending = [...selected];
584
- while (pending.length) {
585
- const row = pending.pop();
586
- if (lower(row.raw_source || row.source) !== 'subagent')
587
- continue;
588
- const parentId = str(row.parent_session_id);
589
- if (!parentId || have.has(parentId))
590
- continue;
591
- const parent = byId.get(parentId);
592
- if (!parent || lower(parent.raw_source || parent.source) !== 'subagent')
593
- continue;
594
- selected.push(parent);
595
- have.add(parentId);
596
- pending.push(parent);
597
- }
598
- return selected;
772
+ return { rows, storage: AGENT_STORAGE_OK };
599
773
  }
600
774
  finally {
601
- db.close();
775
+ finishStateDbRead(db);
602
776
  }
603
777
  }
604
778
  /** Python `agent_session_rows_existing`: batch existence probe; assumes present on any failure so nothing is pruned by mistake. */
@@ -629,12 +803,61 @@ export function agentSessionRowsExisting(dbPath, sessionIds) {
629
803
  return wanted;
630
804
  }
631
805
  finally {
632
- db.close();
806
+ finishStateDbRead(db);
807
+ }
808
+ }
809
+ /** Scheduler admissions are the identity of gateway-fired cron runs, whose session ids stay local-*. */
810
+ function cronAdmissionJobIds(db, terminalOnly = false) {
811
+ const found = new Map();
812
+ const cols = tableColumns(db, 'session_admissions');
813
+ if (!['request_id', 'target_session_id', 'principal_id'].every((col) => cols.has(col)) || (terminalOnly && !cols.has('status')))
814
+ return found;
815
+ // The principal predicate selects the Agent's partial covering index, excluding interactive admissions.
816
+ const rows = db.prepare(`SELECT target_session_id, request_id FROM session_admissions WHERE principal_id = 'cron-owner' AND request_id LIKE 'cron:%'${terminalOnly ? " AND status = 'terminal'" : ''}`).all();
817
+ for (const row of rows) {
818
+ const match = /^cron:([^:]+):([^:]+)$/.exec(row.request_id);
819
+ if (match)
820
+ found.set(row.target_session_id, match[1]);
821
+ }
822
+ return found;
823
+ }
824
+ /** Read-only admission lookup, built once per session listing; older Agent stores have none. */
825
+ export function stateDbCronJobIds(dbPath) {
826
+ if (!existsSync(dbPath))
827
+ return new Map();
828
+ let db;
829
+ try {
830
+ db = openStateDbReadonly(dbPath);
831
+ }
832
+ catch {
833
+ return new Map();
834
+ }
835
+ try {
836
+ return cronAdmissionJobIds(db);
837
+ }
838
+ catch {
839
+ return new Map();
840
+ }
841
+ finally {
842
+ finishStateDbRead(db);
633
843
  }
634
844
  }
845
+ /** Shared job identity rule for completion links and sidebar titles, including legacy job ids with underscores. */
846
+ export function cronSessionJobId(sid, jobIds, admissions) {
847
+ const admitted = admissions.get(sid);
848
+ if (admitted)
849
+ return admitted;
850
+ const legacy = /^cron_(.+)_\d{8}_\d{6}$/.exec(sid);
851
+ if (legacy)
852
+ return legacy[1];
853
+ // Preserve prefix matching for older non-timestamp session ids.
854
+ return [...jobIds].filter((id) => sid.startsWith(`cron_${id}_`)).reduce((best, id) => best === null || id.length > best.length ? id : best, null);
855
+ }
635
856
  /**
636
- * Python `_latest_cron_session_info_for_jobs`: the newest-started cron session for each completed job. A session id
637
- * belongs to the job with the longest matching `cron_{job_id}_` prefix, so `a` never claims `a_b`'s sessions. A
857
+ * Python `_latest_cron_session_info_for_jobs`: the newest-started cron session for each completed job, excluding
858
+ * non-terminal gateway admissions so the next run cannot steal the completion link. A session id
859
+ * belongs to its scheduler admission's job or its legacy timestamp identity. Non-timestamp ids retain the longest
860
+ * matching job prefix, so `a` never claims a known `a_b`'s sessions. A
638
861
  * missing, locked, or unrecognized state.db answers an empty map; callers send no enrichment.
639
862
  */
640
863
  export function latestCronSessionInfo(dbPath, jobIds, completedIds) {
@@ -654,17 +877,23 @@ export function latestCronSessionInfo(dbPath, jobIds, completedIds) {
654
877
  const cols = tableColumns(db, 'sessions');
655
878
  if (!cols.has('id') || !cols.has('source'))
656
879
  return found;
880
+ const admissions = cronAdmissionJobIds(db, true);
657
881
  const count = cols.has('message_count') ? 's.message_count' : 'NULL';
658
882
  const order = cols.has('started_at') ? 'COALESCE(s.started_at, 0) DESC, s.id DESC' : 's.id DESC';
659
- const rows = db.prepare(`SELECT s.id AS id, ${count} AS message_count FROM sessions s WHERE LOWER(COALESCE(s.source, '')) = 'cron' ORDER BY ${order}`).all();
660
- for (const row of rows) {
661
- const sid = typeof row.id === 'string' ? row.id : '';
662
- const owner = ids.filter((id) => sid.startsWith(`cron_${id}_`)).reduce((best, id) => (best === null || id.length > best.length ? id : best), null);
663
- if (owner === null || !wanted.has(owner) || found.has(owner))
664
- continue;
665
- found.set(owner, { session_id: sid, message_count: Number.isFinite(Number(row.message_count)) && row.message_count !== null ? Math.trunc(Number(row.message_count)) : null });
666
- if (found.size === wanted.size)
667
- break;
883
+ const indexed = tableIndexes(db, 'sessions').has('idx_sessions_source_id') ? ' INDEXED BY idx_sessions_source_id' : '';
884
+ const query = db.prepare(`SELECT s.id AS id, ${count} AS message_count FROM sessions s${indexed} WHERE s.source = 'cron' ORDER BY ${order} LIMIT ? OFFSET ?`);
885
+ const batchSize = wanted.size * 8;
886
+ // Stream older rows only if needed: a busy job must not hide another job's completion link.
887
+ for (const [limit, offset] of [[batchSize, 0], [-1, batchSize]]) {
888
+ for (const row of query.iterate(limit, offset)) {
889
+ const sid = typeof row.id === 'string' ? row.id : '';
890
+ const owner = cronSessionJobId(sid, ids, admissions);
891
+ if (owner === null || !wanted.has(owner) || found.has(owner))
892
+ continue;
893
+ found.set(owner, { session_id: sid, message_count: Number.isFinite(Number(row.message_count)) && row.message_count !== null ? Math.trunc(Number(row.message_count)) : null });
894
+ if (found.size === wanted.size)
895
+ return found;
896
+ }
668
897
  }
669
898
  return found;
670
899
  }
@@ -672,7 +901,7 @@ export function latestCronSessionInfo(dbPath, jobIds, completedIds) {
672
901
  return new Map();
673
902
  }
674
903
  finally {
675
- db.close();
904
+ finishStateDbRead(db);
676
905
  }
677
906
  }
678
907
  /** A source label remains display metadata; canonical receipts and ledgers independently retain write ownership. */
@@ -722,36 +951,49 @@ function sessionSources(db, cols, ids) {
722
951
  receiptOwners.set(id, owner);
723
952
  };
724
953
  if (tables.has('state_meta')) {
725
- const read = db.prepare('SELECT value FROM state_meta WHERE key = ?');
954
+ const prefixes = [LOCAL_POLICY_PREFIX, 'gateway.retired_session.v1.', 'gateway.api.binding.v1.'];
955
+ const keys = candidates.flatMap((owner) => prefixes.map((prefix) => `${prefix}${owner}`));
956
+ const metadata = new Map();
957
+ const read = db.prepare('SELECT key, value FROM state_meta WHERE key IN (SELECT value FROM json_each(?))');
958
+ for (let i = 0; i < keys.length; i += 400) {
959
+ for (const row of read.all(JSON.stringify(keys.slice(i, i + 400))))
960
+ metadata.set(row.key, row.value);
961
+ }
962
+ // Keep receipt precedence in candidate order, independent of SQLite's key ordering.
726
963
  for (const owner of candidates) {
727
- const raw = read.get(`${LOCAL_POLICY_PREFIX}${owner}`);
728
- if (raw)
729
- rememberPolicy(owner, policyValue(raw.value));
730
- if (read.get(`gateway.retired_session.v1.${owner}`))
964
+ const key = `${LOCAL_POLICY_PREFIX}${owner}`;
965
+ if (metadata.has(key))
966
+ rememberPolicy(owner, policyValue(metadata.get(key)));
967
+ if (metadata.has(`gateway.retired_session.v1.${owner}`))
731
968
  retired.add(owner);
732
- if (read.get(`gateway.api.binding.v1.${owner}`))
969
+ if (metadata.has(`gateway.api.binding.v1.${owner}`))
733
970
  bindings.add(owner);
734
971
  }
735
972
  }
736
973
  const ledgerIds = new Set();
974
+ const ledgerCandidates = JSON.stringify(candidates);
737
975
  for (const [table, column] of [['session_admissions', 'target_session_id'], ['worker_executions', 'session_id']]) {
738
- if (!tables.has(table))
976
+ if (!tables.has(table) || !candidates.length)
739
977
  continue;
740
978
  const hasLineage = tableColumns(db, table).has('lineage_json');
741
979
  for (let i = 0; i < candidates.length; i += 400) {
742
980
  const chunk = candidates.slice(i, i + 400);
743
981
  const marks = chunk.map(() => '?').join(',');
744
- const linked = hasLineage ? ` OR EXISTS (SELECT 1 FROM json_each(lineage_json) WHERE value IN (${marks}))` : '';
745
- const found = db.prepare(`SELECT ${column} AS id, ${hasLineage ? 'lineage_json' : 'NULL AS lineage_json'} FROM ${table} WHERE ${column} IN (${marks})${linked}`).all(...chunk, ...(hasLineage ? chunk : []));
746
- for (const row of found) {
982
+ for (const row of db.prepare(`SELECT ${column} AS id FROM ${table} WHERE ${column} IN (${marks})`).all(...chunk))
747
983
  ledgerIds.add(row.id);
748
- if (row.lineage_json !== null) {
749
- const lineage = JSON.parse(row.lineage_json);
750
- if (!Array.isArray(lineage) || !lineage.every((v) => typeof v === 'string'))
751
- throw new Error('Unreadable Agent admission lineage');
752
- for (const sid of lineage)
753
- ledgerIds.add(sid);
754
- }
984
+ }
985
+ if (hasLineage) {
986
+ // Bind the complete set once so unrelated history never crosses into JavaScript, even across candidate chunks.
987
+ const scan = db.prepare(`SELECT ${column} AS id, lineage_json FROM ${table} WHERE lineage_json IS NOT NULL AND
988
+ (${column} IN (SELECT value FROM json_each(?)) OR EXISTS
989
+ (SELECT 1 FROM json_each(lineage_json) WHERE value IN (SELECT value FROM json_each(?))))`);
990
+ for (const row of scan.iterate(ledgerCandidates, ledgerCandidates)) {
991
+ const lineage = JSON.parse(row.lineage_json);
992
+ if (!Array.isArray(lineage) || !lineage.every((v) => typeof v === 'string'))
993
+ throw new Error('Unreadable Agent admission lineage');
994
+ ledgerIds.add(row.id);
995
+ for (const sid of lineage)
996
+ ledgerIds.add(sid);
755
997
  }
756
998
  }
757
999
  }
@@ -769,12 +1011,13 @@ function sessionSources(db, cols, ids) {
769
1011
  rememberPolicy(owner, policy);
770
1012
  }
771
1013
  }
772
- const kanban = new Set(kanbanOwnedIds(db, cols, ids.filter((id) => rows.has(id))));
1014
+ const kanban = new Set(kanbanOwnedIds(db, cols, new Map([...lineages].filter(([id]) => rows.has(id)))));
773
1015
  for (const id of ids) {
774
1016
  const row = rows.get(id);
775
1017
  let source = kanban.has(id) ? 'kanban' : str(row?.source);
776
1018
  let canonical = false;
777
1019
  let retiredId = false;
1020
+ let mode;
778
1021
  for (const segment of lineages.get(id) ?? [id]) {
779
1022
  const chatId = str(rows.get(segment)?.chat_id);
780
1023
  for (const owner of new Set([segment, chatId, receiptOwners.get(segment)].filter((v) => Boolean(v)))) {
@@ -788,8 +1031,12 @@ function sessionSources(db, cols, ids) {
788
1031
  // A broken receipt never releases its owner or a physical row naming that owner.
789
1032
  canonical = true;
790
1033
  const origin = policy?.policy;
791
- if (origin && typeof origin === 'object' && !Array.isArray(origin) && str(origin.source))
792
- source = str(origin.source);
1034
+ if (origin && typeof origin === 'object' && !Array.isArray(origin)) {
1035
+ const frozen = origin;
1036
+ if (str(frozen.source))
1037
+ source = str(frozen.source);
1038
+ mode ??= frozen.safe_mode === true ? 'safe' : frozen.ignore_user_config === true ? 'ignore_config' : 'normal';
1039
+ }
793
1040
  }
794
1041
  }
795
1042
  const authority = canonical;
@@ -798,10 +1045,38 @@ function sessionSources(db, cols, ids) {
798
1045
  canonical = true;
799
1046
  if (!row && !canonical && !retiredId)
800
1047
  continue;
801
- sources.set(id, retiredId && !authority ? { source, canonical: true, reason: RETIRED_READ_ONLY_REASON } : canonical ? { source, canonical: true } : source);
1048
+ const owned = { source, canonical: true, ...(mode ? { execution_mode: mode } : {}) };
1049
+ sources.set(id, retiredId && !authority ? { ...owned, reason: RETIRED_READ_ONLY_REASON } : canonical ? owned : source);
802
1050
  }
803
1051
  return sources;
804
1052
  }
1053
+ /** TAL-1040: the request-id prefix Talaria mints for the diagnostic chats it asks an owner to create; its receipt keeps it. */
1054
+ export const WEB_DIAGNOSTIC_REQUEST_PREFIX = 'talaria-web:';
1055
+ /** Owner ids whose creation receipt carries Talaria's request prefix: chats Web started, shown whatever the external-session setting. */
1056
+ export function stateDbWebDiagnosticIds(dbPath) {
1057
+ if (!existsSync(dbPath))
1058
+ return [];
1059
+ let db;
1060
+ try {
1061
+ db = openStateDbReadonly(dbPath);
1062
+ }
1063
+ catch {
1064
+ return [];
1065
+ }
1066
+ try {
1067
+ if (!tableColumns(db, 'state_meta').has('value'))
1068
+ return [];
1069
+ const rows = db.prepare("SELECT key FROM state_meta WHERE key GLOB ? AND json_valid(value) AND json_extract(value, '$.request_id') GLOB ?")
1070
+ .all(`${LOCAL_POLICY_PREFIX}*`, `${WEB_DIAGNOSTIC_REQUEST_PREFIX}*`);
1071
+ return rows.map((row) => row.key.slice(LOCAL_POLICY_PREFIX.length));
1072
+ }
1073
+ catch {
1074
+ return [];
1075
+ }
1076
+ finally {
1077
+ finishStateDbRead(db);
1078
+ }
1079
+ }
805
1080
  /** Batched source/ownership probe. An unreadable authority answers null so callers fail closed. */
806
1081
  export function stateDbSessionSources(dbPath, sessionIds) {
807
1082
  const ids = [...new Set([...sessionIds].map((s) => s.trim()).filter(Boolean))];
@@ -815,14 +1090,13 @@ export function stateDbSessionSources(dbPath, sessionIds) {
815
1090
  return null;
816
1091
  }
817
1092
  try {
818
- db.exec('BEGIN');
819
1093
  return sessionSources(db, tableColumns(db, 'sessions'), ids);
820
1094
  }
821
1095
  catch {
822
1096
  return null;
823
1097
  }
824
1098
  finally {
825
- db.close();
1099
+ finishStateDbRead(db);
826
1100
  }
827
1101
  }
828
1102
  /** The same marker parser serves conservative history boundaries and exact-parent ownership walks. */
@@ -851,8 +1125,8 @@ function isExplicitForkChild(row, includeReset) {
851
1125
  }
852
1126
  /**
853
1127
  * Python `_resume_lineage_ids`: `sid` and the compression ancestors a resume loads, root first. Each hop's parent ended
854
- * `compression` and the child is no branch, reset, delegate or tool fork. The walk stops at a cycle or the Agent's
855
- * 1000-hop `_CHAIN_CAP`; without the lineage columns it is `[sid]`.
1128
+ * `compression` and the child is no branch, reset, delegate or tool fork. The walk stops at a cycle or
1129
+ * `RESUME_LINEAGE_CAP` ids; without the lineage columns it is `[sid]`.
856
1130
  */
857
1131
  function resumeLineageWalker(db, cols) {
858
1132
  if (!['parent_session_id', 'end_reason'].every((c) => cols.has(c)))
@@ -863,7 +1137,7 @@ function resumeLineageWalker(db, cols) {
863
1137
  let current = select.get(sid);
864
1138
  if (!current || isExplicitForkChild(current, false))
865
1139
  return chain;
866
- while (chain.length < 1000) {
1140
+ while (chain.length < RESUME_LINEAGE_CAP) {
867
1141
  const parentId = str(current.parent_session_id);
868
1142
  if (!parentId || chain.includes(parentId) || isExplicitForkChild(current, true))
869
1143
  break;
@@ -880,12 +1154,10 @@ function resumeLineageWalker(db, cols) {
880
1154
  * TAL-793 (Agent `is_kanban_owned_session`): the `ids` whose compression lineage has a `kanban` `source` or
881
1155
  * `created_source` (schema v31; `source` alone decides before it), a Kanban worker's transcript only its dispatcher continues.
882
1156
  */
883
- function kanbanOwnedIds(db, cols, ids) {
884
- if (!ids.length)
1157
+ function kanbanOwnedIds(db, cols, lineages) {
1158
+ if (!lineages.size)
885
1159
  return [];
886
- const walk = resumeLineageWalker(db, cols);
887
- const lineages = ids.map(walk);
888
- const segments = [...new Set(lineages.flat())];
1160
+ const segments = [...new Set([...lineages.values()].flat())];
889
1161
  const match = cols.has('created_source') ? "(source = 'kanban' OR created_source = 'kanban')" : "source = 'kanban'";
890
1162
  const kanban = new Set();
891
1163
  for (let i = 0; i < segments.length; i += 500) {
@@ -893,7 +1165,7 @@ function kanbanOwnedIds(db, cols, ids) {
893
1165
  for (const row of db.prepare(`SELECT id FROM sessions WHERE id IN (${chunk.map(() => '?').join(',')}) AND ${match}`).all(...chunk))
894
1166
  kanban.add(row.id);
895
1167
  }
896
- return ids.filter((_, i) => lineages[i].some((sid) => kanban.has(sid)));
1168
+ return [...lineages].filter(([, lineage]) => lineage.some((sid) => kanban.has(sid))).map(([id]) => id);
897
1169
  }
898
1170
  /** Python `state_db_has_session`: true only when `sid` is a row of the sessions table; a missing or unreadable db is false. */
899
1171
  export function stateDbHasSession(dbPath, sid) {
@@ -916,7 +1188,33 @@ export function stateDbHasSession(dbPath, sid) {
916
1188
  return false;
917
1189
  }
918
1190
  finally {
919
- db.close();
1191
+ finishStateDbRead(db);
1192
+ }
1193
+ }
1194
+ /** True when a session has a live message; never materializes its transcript or compaction archive. */
1195
+ export function stateDbSessionHasMessages(dbPath, sid) {
1196
+ const id = sid.trim();
1197
+ if (!id || !existsSync(dbPath))
1198
+ return false;
1199
+ let db;
1200
+ try {
1201
+ db = openStateDbReadonly(dbPath);
1202
+ }
1203
+ catch {
1204
+ return false;
1205
+ }
1206
+ try {
1207
+ const cols = tableColumns(db, 'messages');
1208
+ if (!cols.has('session_id'))
1209
+ return false;
1210
+ const active = cols.has('active') ? ' AND (active IS NULL OR active != 0)' : '';
1211
+ return db.prepare(`SELECT 1 FROM messages WHERE session_id = ?${active} LIMIT 1`).get(id) !== undefined;
1212
+ }
1213
+ catch {
1214
+ return false;
1215
+ }
1216
+ finally {
1217
+ finishStateDbRead(db);
920
1218
  }
921
1219
  }
922
1220
  /**
@@ -939,7 +1237,7 @@ export function cheapChangeFingerprint(dbPath, onError) {
939
1237
  }
940
1238
  }
941
1239
  finally {
942
- db.close();
1240
+ finishStateDbRead(db);
943
1241
  }
944
1242
  for (const path of [dbPath, `${dbPath}-wal`]) {
945
1243
  try {
@@ -971,22 +1269,28 @@ export function cheapChangeFingerprint(dbPath, onError) {
971
1269
  return null;
972
1270
  }
973
1271
  }
974
- let listingWorker = null;
1272
+ const STATE_DB_WORKER_LANES = ['listing', 'authority', 'session'];
1273
+ const listingWorkers = new Map();
1274
+ const listingReadScope = new AsyncLocalStorage();
1275
+ /** Keep a sidebar phase's nested reads on its lane without changing interactive callers' defaults. */
1276
+ export function withStateDbListingReads(read) { return listingReadScope.run('listing', read); }
975
1277
  let nextCallId = 0;
976
- /** Set while `stopStateDbWorker` waits for the worker to exit; calls then reject instead of starting another worker. */
1278
+ /** Set until every worker exits during shutdown; calls then reject instead of starting another worker. */
977
1279
  let stoppingWorker = false;
978
1280
  /** Reject every call still waiting on `current` and forget it, so the next call starts a fresh worker. */
979
1281
  function retireListingWorker(current, error) {
980
- if (listingWorker === current)
981
- listingWorker = null;
1282
+ for (const [lane, worker] of listingWorkers)
1283
+ if (worker === current)
1284
+ listingWorkers.delete(lane);
982
1285
  for (const call of current.pending.values())
983
1286
  call.reject(error);
984
1287
  current.pending.clear();
985
1288
  }
986
- /** The lazily started, long-lived listing worker; requests run one at a time in arrival order. */
987
- function startedListingWorker() {
988
- if (listingWorker)
989
- return listingWorker;
1289
+ /** Each lane lazily starts one long-lived worker and keeps its own FIFO and read-only handle cache. */
1290
+ function startedListingWorker(lane) {
1291
+ const existing = listingWorkers.get(lane);
1292
+ if (existing)
1293
+ return existing;
990
1294
  // Source runs (vitest) load the TypeScript worker; the build runs the compiled one beside this module.
991
1295
  const ext = import.meta.url.endsWith('.ts') ? 'ts' : 'js';
992
1296
  const current = { worker: new Worker(new URL(`./state-db-worker.${ext}`, import.meta.url)), pending: new Map() };
@@ -1002,20 +1306,26 @@ function startedListingWorker() {
1002
1306
  });
1003
1307
  current.worker.on('error', (error) => { retireListingWorker(current, error); });
1004
1308
  current.worker.on('exit', (code) => { retireListingWorker(current, new Error(`state.db worker exited with code ${String(code)}`)); });
1005
- listingWorker = current;
1309
+ listingWorkers.set(lane, current);
1006
1310
  return current;
1007
1311
  }
1008
- function callListingWorker(call) {
1312
+ function callListingWorker(call, lane) {
1009
1313
  // A multi-read caller the stop interrupted goes on to its next read; that read must not restart the worker.
1010
1314
  if (stoppingWorker)
1011
1315
  return Promise.reject(new Error('state.db worker stopped'));
1012
- const current = startedListingWorker();
1316
+ const target = lane ?? listingReadScope.getStore() ?? (call.op === 'session' ? SESSION_READ_LANES[call.read] : call.op === 'sources' || call.op === 'existing' ? 'authority' : 'listing');
1317
+ const current = startedListingWorker(target);
1013
1318
  const id = ++nextCallId;
1319
+ const timing = startupTimingScope.getStore();
1014
1320
  return new Promise((resolve, reject) => {
1015
- current.pending.set(id, { resolve, reject });
1321
+ current.pending.set(id, { resolve: (reply) => {
1322
+ if (reply.timing)
1323
+ timing?.workerRead(target, call.op === 'session' ? call.read : call.op, reply.timing);
1324
+ resolve(reply);
1325
+ }, reject });
1016
1326
  // A call in flight keeps the process alive until it settles; an idle worker never does.
1017
1327
  current.worker.ref();
1018
- current.worker.postMessage({ ...call, id, delayMs: readDelayMs });
1328
+ current.worker.postMessage({ ...call, id, delayMs: readDelayMs, queuedAtNs: process.hrtime.bigint() });
1019
1329
  });
1020
1330
  }
1021
1331
  /** `readImportableAgentSessionRows` on the listing worker, so a slow `state.db` never blocks the event loop. Rejects when the worker fails. */
@@ -1028,6 +1338,15 @@ export async function readImportableAgentSessionRowsAsync(dbPath, opts = {}) {
1028
1338
  throw new Error(reply.error);
1029
1339
  return reply.value;
1030
1340
  }
1341
+ /** One worker crossing for all sidebar windows and their storage health. */
1342
+ export async function readImportableAgentSessionRowBatchesAsync(dbPath, queries) {
1343
+ const reply = await callListingWorker({ op: 'rowBatches', dbPath, queries: queries.map((opts) => ({ ...opts, log: undefined })) });
1344
+ for (const line of reply.logs)
1345
+ queries[0]?.log?.(line);
1346
+ if (reply.error !== undefined)
1347
+ throw new Error(reply.error);
1348
+ return reply.value;
1349
+ }
1031
1350
  /** `cheapChangeFingerprint` on the listing worker; a worker failure reports as a failed change check. */
1032
1351
  export async function cheapChangeFingerprintAsync(dbPath, onError) {
1033
1352
  let reply;
@@ -1051,8 +1370,10 @@ export async function stateDbStorageAsync(dbPath) {
1051
1370
  return stateDbReadFailure(error);
1052
1371
  }
1053
1372
  }
1054
- /** `stateDbSessionSources` on the listing worker; a worker failure is an unreadable state.db (null), so owners stay locked. */
1373
+ /** `stateDbSessionSources` on the authority worker; a worker failure is an unreadable state.db (null), so owners stay locked. Without a state.db it answers here, opening nothing. */
1055
1374
  export async function stateDbSessionSourcesAsync(dbPath, sessionIds) {
1375
+ if (!existsSync(dbPath))
1376
+ return stateDbSessionSources(dbPath, sessionIds);
1056
1377
  try {
1057
1378
  return (await callListingWorker({ op: 'sources', dbPath, ids: [...sessionIds] })).value;
1058
1379
  }
@@ -1060,7 +1381,16 @@ export async function stateDbSessionSourcesAsync(dbPath, sessionIds) {
1060
1381
  return null;
1061
1382
  }
1062
1383
  }
1063
- /** `agentSessionRowsExisting` on the listing worker; a worker failure assumes every row present, so nothing is pruned. */
1384
+ /** `stateDbWebDiagnosticIds` on the listing worker; a failed read lists none, so the setting still governs every other row. */
1385
+ export async function stateDbWebDiagnosticIdsAsync(dbPath) {
1386
+ try {
1387
+ return (await callListingWorker({ op: 'webDiagnostic', dbPath })).value;
1388
+ }
1389
+ catch {
1390
+ return [];
1391
+ }
1392
+ }
1393
+ /** `agentSessionRowsExisting` on the authority worker; a worker failure assumes every row present, so nothing is pruned. */
1064
1394
  export async function agentSessionRowsExistingAsync(dbPath, sessionIds) {
1065
1395
  const ids = [...sessionIds];
1066
1396
  try {
@@ -1070,19 +1400,60 @@ export async function agentSessionRowsExistingAsync(dbPath, sessionIds) {
1070
1400
  return new Set(ids.map((s) => s.trim()).filter(Boolean));
1071
1401
  }
1072
1402
  }
1073
- /** Test hook: the listing worker exits abruptly while this call is in flight. */
1074
- export function crashStateDbWorkerForTests() {
1075
- return callListingWorker({ op: 'crash' });
1403
+ /**
1404
+ * TAL-923: the single-session readers behind chat start, session detail, and the mutation gates, run on the worker.
1405
+ * TAL-1162: and the transcript reads behind turn history, settlement, and the detail's page tail.
1406
+ * TAL-1256: shared cron admission identities and completion links use this same dispatcher.
1407
+ */
1408
+ export const SESSION_READS = { stateDbHasSession, stateDbSessionHasMessages, insightsSessionRows, countConversationRounds, stateDbTailToolContent, stateDbLineageReport, stateDbCronJobIds, latestCronSessionInfo, stateDbLocalTargetRead, stateDbSessionRow, stateDbSessionMessages, stateDbCompressionLineage, stateDbSessionRead, stateDbSessionTailRead, stateDbSettlementRead };
1409
+ /** Exhaustive policy: authority never waits for transcript materialization; sidebar-scoped probes stay on listing. */
1410
+ const SESSION_READ_LANES = {
1411
+ stateDbHasSession: 'authority', stateDbSessionHasMessages: 'authority', stateDbLocalTargetRead: 'authority', stateDbSessionRow: 'authority',
1412
+ insightsSessionRows: 'session', countConversationRounds: 'session', stateDbTailToolContent: 'session', stateDbLineageReport: 'session',
1413
+ stateDbCronJobIds: 'session', latestCronSessionInfo: 'session', stateDbSessionMessages: 'session', stateDbCompressionLineage: 'session',
1414
+ stateDbSessionRead: 'session', stateDbSessionTailRead: 'session', stateDbSettlementRead: 'session',
1415
+ };
1416
+ /**
1417
+ * A `SESSION_READS` reader on its authority or transcript lane, so expensive reads cannot hold up authority. Each reader
1418
+ * already answers a failed read with `fallback`'s value; a worker failure answers it too. Without a state.db the reader
1419
+ * answers here, as it opens nothing then.
1420
+ */
1421
+ export async function stateDbSessionReadAsync(fallback, read, ...args) {
1422
+ if (!existsSync(args[0]))
1423
+ return SESSION_READS[read](...args);
1424
+ try {
1425
+ const reply = await callListingWorker({ op: 'session', read, args });
1426
+ return reply.error === undefined ? reply.value : fallback;
1427
+ }
1428
+ catch {
1429
+ return fallback;
1430
+ }
1076
1431
  }
1077
- /** Stop the listing worker (server shutdown). Calls in flight reject; a later call starts a new worker. */
1432
+ /** Test hook: crash one lane, or all when omitted, while its calls are in flight. */
1433
+ export function crashStateDbWorkerForTests(lane) {
1434
+ return Promise.all((lane ? [lane] : STATE_DB_WORKER_LANES).map((target) => callListingWorker({ op: 'crash' }, target)));
1435
+ }
1436
+ /** Stop every worker (server shutdown). Calls in flight reject; later calls start fresh workers. */
1078
1437
  export async function stopStateDbWorker() {
1079
- const current = listingWorker;
1080
- if (!current)
1438
+ closeStateDbs();
1439
+ const workers = [...listingWorkers.values()];
1440
+ if (!workers.length)
1081
1441
  return;
1082
1442
  stoppingWorker = true;
1083
1443
  try {
1084
- retireListingWorker(current, new Error('state.db worker stopped'));
1085
- await current.worker.terminate();
1444
+ await Promise.all(workers.map(async (current) => {
1445
+ retireListingWorker(current, new Error('state.db worker stopped'));
1446
+ const exited = new Promise((resolve) => { current.worker.once('exit', () => { resolve(); }); });
1447
+ // Idle workers close their cache through process.exit; a blocked read still cannot delay shutdown.
1448
+ const force = setTimeout(() => { void current.worker.terminate(); }, 100);
1449
+ try {
1450
+ current.worker.postMessage({ op: 'stop', id: 0, delayMs: 0 });
1451
+ await exited;
1452
+ }
1453
+ finally {
1454
+ clearTimeout(force);
1455
+ }
1456
+ }));
1086
1457
  }
1087
1458
  finally {
1088
1459
  stoppingWorker = false;
@@ -1091,7 +1462,7 @@ export async function stopStateDbWorker() {
1091
1462
  // ── message reader ────────────────────────────────────────────────────────
1092
1463
  const STATE_DB_CONTENT_JSON_PREFIX = '\0json:';
1093
1464
  // `display_kind` / `display_metadata`: the Agent's own tag for rows it writes itself (steers, delivered notifications; TAL-371).
1094
- const OPTIONAL_MESSAGE_COLUMNS = ['tool_call_id', 'tool_calls', 'tool_name', 'reasoning', 'reasoning_details', 'codex_reasoning_items', 'reasoning_content', 'codex_message_items', 'api_content', 'display_kind', 'display_metadata', 'absorbed_message_uids'];
1465
+ const OPTIONAL_MESSAGE_COLUMNS = ['tool_call_id', 'tool_calls', 'tool_name', 'reasoning', 'reasoning_details', 'codex_reasoning_items', 'reasoning_content', 'codex_message_items', 'api_content', 'display_kind', 'display_metadata', 'absorbed_message_uids', '_compressed_summary'];
1095
1466
  const JSON_MESSAGE_COLUMNS = new Set(['tool_calls', 'reasoning_details', 'codex_reasoning_items', 'codex_message_items', 'display_metadata', 'absorbed_message_uids']);
1096
1467
  /** Python `_decode_state_db_content`: the Agent's sentinel-prefixed structured content, left untouched otherwise. */
1097
1468
  function decodeStateDbContent(value) {
@@ -1123,11 +1494,28 @@ function jsonLoadsIfString(value) {
1123
1494
  return value;
1124
1495
  }
1125
1496
  }
1497
+ // Agent v0.22.3's ordinary kept-tail handoff. Only its explicit flag and exact wrapper prove a carrier.
1498
+ const PRIOR_CONTEXT_HEADER = '[PRIOR CONTEXT — for reference only; not a new message]';
1499
+ const COMPACTION_SUMMARY_DELIMITER = '\n\n[END OF PRIOR CONTEXT — COMPACTION SUMMARY BELOW]\n\n';
1500
+ function compactionCarrier(row) {
1501
+ if (row._compressed_summary !== 1 && row._compressed_summary !== true)
1502
+ return null;
1503
+ const content = decodeStateDbContent(row.content);
1504
+ if ((row.role !== 'user' && row.role !== 'assistant') || typeof content !== 'string' || !content.startsWith(`${PRIOR_CONTEXT_HEADER}\n`))
1505
+ return null;
1506
+ const boundary = content.indexOf(COMPACTION_SUMMARY_DELIMITER, PRIOR_CONTEXT_HEADER.length);
1507
+ if (boundary < 0)
1508
+ return null;
1509
+ // Remove only the separators the Agent added; preserve the kept reply's own whitespace.
1510
+ const prior = content.slice(PRIOR_CONTEXT_HEADER.length + 1, boundary);
1511
+ const summary = content.slice(boundary + COMPACTION_SUMMARY_DELIMITER.length).trim();
1512
+ return prior && summary ? { prior, summary } : null;
1513
+ }
1126
1514
  /** Python `_project_state_db_message`: one row → WebUI message (`tool_name → name`, empty optionals omitted). */
1127
1515
  function projectStateDbMessage(row, hasId) {
1128
- const msg = { role: row.role, content: decodeStateDbContent(row.content), timestamp: row.timestamp };
1516
+ const msg = { role: row.role, content: compactionCarrier(row)?.prior ?? decodeStateDbContent(row.content), timestamp: row.timestamp };
1129
1517
  for (const col of OPTIONAL_MESSAGE_COLUMNS) {
1130
- if (!(col in row))
1518
+ if (col === '_compressed_summary' || !(col in row))
1131
1519
  continue;
1132
1520
  const value = row[col];
1133
1521
  if (value === null || value === undefined || value === '')
@@ -1159,7 +1547,7 @@ function stateDbGeneration(dbPath) {
1159
1547
  }
1160
1548
  // Agent v0.21.6's exact in-flight request wrapper. Unknown rewrites are not copy evidence.
1161
1549
  const INFLIGHT_REPLAY_HEADER = '[STILL IN PROGRESS — this is the active request, restated after the compaction boundary because it was not finished yet. Continue it; do not start over.]';
1162
- /** The payload comparison ignores only the Agent's documented replay wrapper and dropped api_content. */
1550
+ /** Compare the kept payload after the Agent's exact request/carrier wrappers and stale api_content are removed. */
1163
1551
  function replayPayload(row) {
1164
1552
  const message = projectStateDbMessage(row, false);
1165
1553
  delete message.timestamp;
@@ -1190,7 +1578,7 @@ function replayPayload(row) {
1190
1578
  }
1191
1579
  message.content = content;
1192
1580
  delete message.api_content; // _reappend_inflight_user_task drops this stale model-facing sidecar.
1193
- return { copyKey, replayKey: JSON.stringify(message), replay };
1581
+ return { copyKey, replayKey: JSON.stringify(message), replay, carrier: compactionCarrier(row)?.summary ?? null };
1194
1582
  }
1195
1583
  /**
1196
1584
  * Compare adjacent generations, never all rows of one UID with its oldest archive. The archive retains
@@ -1215,13 +1603,15 @@ function replayIdentities(db, chain, rows, available) {
1215
1603
  const payload = replayPayload(row);
1216
1604
  const prior = previous.get(uid);
1217
1605
  const copy = payload.copyKey === prior?.copyKey && row.timestamp === prior.timestamp;
1218
- const restatement = prior && payload.replay && payload.replayKey === prior.replayKey && (prior.active !== 0 || prior.compacted === 1);
1606
+ const restatement = payload.replayKey === prior?.replayKey && ((payload.replay && (prior.active !== 0 || prior.compacted === 1)) ||
1607
+ (payload.carrier && row.timestamp === prior.timestamp));
1219
1608
  const identity = prior && (copy || restatement) ? prior.identity : Number(row.id);
1220
1609
  const changed = prior ? (copy || restatement ? prior.changed : true) : false;
1221
1610
  const sourceKey = prior && (copy || restatement) ? prior.sourceKey : stateDbMessageFingerprint(projectStateDbMessage(row, false));
1611
+ const summaryId = payload.carrier && payload.carrier === prior?.carrier && row.timestamp === prior.timestamp ? prior.summaryId : Number(row.id);
1222
1612
  if (prior && row.role !== 'tool' && liveIds.has(row.id))
1223
- identities.set(Number(row.id), { id: identity, changed, sourceKey });
1224
- previous.set(uid, { ...payload, identity, changed, sourceKey, timestamp: row.timestamp, active: row.active, compacted: row.compacted });
1613
+ identities.set(Number(row.id), { id: identity, changed, sourceKey, summaryId });
1614
+ previous.set(uid, { ...payload, identity, changed, sourceKey, summaryId, timestamp: row.timestamp, active: row.active, compacted: row.compacted });
1225
1615
  }
1226
1616
  return identities;
1227
1617
  }
@@ -1235,6 +1625,16 @@ export function stateDbSessionMessages(dbPath, sid, opts = {}) {
1235
1625
  export function stateDbSessionRead(dbPath, sid, opts = {}) {
1236
1626
  return readStateDbSession(dbPath, sid, opts);
1237
1627
  }
1628
+ /**
1629
+ * A settlement's reads in one request: the transcript under `recorded` (the session's `state_db_lineage`), the lineage
1630
+ * the Agent's compression rotations give it now, and, when that differs from `recorded`, the transcript under it.
1631
+ */
1632
+ export function stateDbSettlementRead(dbPath, sid, recorded) {
1633
+ const read = stateDbSessionRead(dbPath, sid, { stitch: false, lineage: recorded?.[0] === sid ? recorded : null });
1634
+ const lineage = stateDbCompressionLineage(dbPath, sid);
1635
+ const moved = JSON.stringify(lineage) !== JSON.stringify(recorded ?? [sid]);
1636
+ return { ...read, lineage, rotated: moved ? stateDbSessionRead(dbPath, sid, { stitch: false, lineage: lineage.length > 1 && lineage[0] === sid ? lineage : null }) : null };
1637
+ }
1238
1638
  /** The same snapshot and provenance projection, restricted to physical rows past the seen marker. */
1239
1639
  export function stateDbSessionTailRead(dbPath, sid, afterId, opts = {}) {
1240
1640
  return readStateDbSession(dbPath, sid, { ...opts, stitch: false, afterId });
@@ -1248,11 +1648,10 @@ function readStateDbSession(dbPath, sid, opts) {
1248
1648
  try {
1249
1649
  db = openStateDbReadonly(dbPath);
1250
1650
  }
1251
- catch {
1252
- return none;
1651
+ catch (error) {
1652
+ return { ...none, reason: stateDbReadFailure(error).reason };
1253
1653
  }
1254
1654
  try {
1255
- db.exec('BEGIN'); // The live rows and their archived predecessors must describe one snapshot.
1256
1655
  const available = tableColumns(db, 'messages');
1257
1656
  if (!['role', 'content', 'timestamp'].every((c) => available.has(c)))
1258
1657
  return { ...none, ok: available.size > 0 };
@@ -1288,7 +1687,7 @@ function readStateDbSession(dbPath, sid, opts) {
1288
1687
  const rangeArgs = opts.afterId === undefined ? [] : [opts.afterId];
1289
1688
  const order = hasId ? 'id' : 'timestamp';
1290
1689
  const rows = db.prepare(`SELECT ${selected.join(', ')}, session_id FROM messages WHERE session_id IN (${chain.map(() => '?').join(', ')})${activeClause}${range} ORDER BY ${order} ASC`).all(...chain, ...rangeArgs);
1291
- const projected = rows.map((row) => projectStateDbMessage(row, hasId));
1690
+ let projected = rows.map((row) => projectStateDbMessage(row, hasId));
1292
1691
  // TAL-709: an in-place compaction archives the live rows and re-inserts each as a copy that keeps its role and timestamp
1293
1692
  // (content may be pruned). A copy takes the id of the oldest archived row it copies, so the merge sees a row it has
1294
1693
  // already read instead of a new one. Only a compaction archive (`compacted = 1`) vouches for a changed copy: a rewound
@@ -1307,8 +1706,9 @@ function readStateDbSession(dbPath, sid, opts) {
1307
1706
  projected[i]._state_db_row_id = twin;
1308
1707
  }
1309
1708
  }
1709
+ let identities;
1310
1710
  if (hasId && ['message_uid', 'active', 'compacted'].every((c) => available.has(c))) {
1311
- const identities = replayIdentities(db, local?.context ?? chain, rows, available);
1711
+ identities = replayIdentities(db, local?.context ?? chain, rows, available);
1312
1712
  for (const [i, row] of rows.entries()) {
1313
1713
  const identity = identities.get(Number(row.id));
1314
1714
  // A known changed generation overrides even a coincidental legacy timestamp twin.
@@ -1321,6 +1721,13 @@ function readStateDbSession(dbPath, sid, opts) {
1321
1721
  }
1322
1722
  }
1323
1723
  }
1724
+ // The kept reply and summary have independent archive identities; neither can consume the other's copy.
1725
+ const withCarrierSummary = (row, message) => {
1726
+ const carrier = compactionCarrier(row);
1727
+ return carrier ? [message, { role: 'assistant', content: carrier.summary, timestamp: row.timestamp,
1728
+ _context_compression_marker: true, ...(hasId ? { _state_db_row_id: identities?.get(Number(row.id))?.summaryId ?? row.id } : {}) }] : [message];
1729
+ };
1730
+ projected = rows.flatMap((row, i) => withCarrierSummary(row, projected[i]));
1324
1731
  // TAL-493: the database file's identity, so a marker taken in a replaced state.db is never applied to its successor.
1325
1732
  const generation = stateDbGeneration(dbPath);
1326
1733
  if (hasId) {
@@ -1333,7 +1740,7 @@ function readStateDbSession(dbPath, sid, opts) {
1333
1740
  if (!anchor) {
1334
1741
  const row = db.prepare(`SELECT ${selected.join(', ')}, session_id FROM messages WHERE session_id IN (${chain.map(() => '?').join(', ')}) AND id = ?${activeClause} LIMIT 1`).get(...chain, opts.afterId);
1335
1742
  if (row)
1336
- anchor = { ...projectStateDbMessage(row, true), _state_db_generation: generation };
1743
+ anchor = { ...withCarrierSummary(row, projectStateDbMessage(row, true)).at(-1), _state_db_generation: generation };
1337
1744
  }
1338
1745
  }
1339
1746
  let requiresFullRead = false;
@@ -1352,7 +1759,7 @@ function readStateDbSession(dbPath, sid, opts) {
1352
1759
  return { ...none, reason: stateDbReadFailure(error).reason };
1353
1760
  }
1354
1761
  finally {
1355
- db.close();
1762
+ finishStateDbRead(db);
1356
1763
  }
1357
1764
  }
1358
1765
  /**
@@ -1372,7 +1779,6 @@ export function stateDbCompressionLineage(dbPath, sid) {
1372
1779
  return lineage;
1373
1780
  }
1374
1781
  try {
1375
- db.exec('BEGIN');
1376
1782
  const local = localLineages(db, lineage[0]).get(lineage[0]);
1377
1783
  if (local)
1378
1784
  return [...local.ids];
@@ -1398,7 +1804,7 @@ export function stateDbCompressionLineage(dbPath, sid) {
1398
1804
  return lineage.slice(0, 1);
1399
1805
  }
1400
1806
  finally {
1401
- db.close();
1807
+ finishStateDbRead(db);
1402
1808
  }
1403
1809
  }
1404
1810
  function lineageReportRow(row, role) {
@@ -1408,7 +1814,7 @@ function lineageReportRow(row, role) {
1408
1814
  end_reason: row.end_reason ?? null, active: ended === null, archived: false,
1409
1815
  };
1410
1816
  }
1411
- const emptyLineageReport = (sid) => ({
1817
+ export const emptyLineageReport = (sid) => ({
1412
1818
  mutation: false, found: false, session_id: sid, lineage_key: sid, tip_session_id: sid, total_segments: 0, materialized_segments: 0, segments: [], children: [], manual_review: false,
1413
1819
  });
1414
1820
  /**
@@ -1429,7 +1835,6 @@ export function stateDbLineageReport(dbPath, rawSid, maxHops = COMPRESSION_CHAIN
1429
1835
  return emptyLineageReport(sid);
1430
1836
  }
1431
1837
  try {
1432
- db.exec('BEGIN');
1433
1838
  const local = localLineages(db, sid).get(sid);
1434
1839
  const cols = tableColumns(db, 'sessions');
1435
1840
  if (!['id', 'parent_session_id', 'end_reason'].every((c) => cols.has(c)))
@@ -1485,7 +1890,7 @@ export function stateDbLineageReport(dbPath, rawSid, maxHops = COMPRESSION_CHAIN
1485
1890
  return emptyLineageReport(sid);
1486
1891
  }
1487
1892
  finally {
1488
- db.close();
1893
+ finishStateDbRead(db);
1489
1894
  }
1490
1895
  }
1491
1896
  /** The `sessions` row for `sid` (source and lifecycle columns), or null. */
@@ -1511,7 +1916,7 @@ export function stateDbSessionRow(dbPath, sid) {
1511
1916
  return null;
1512
1917
  }
1513
1918
  finally {
1514
- db.close();
1919
+ finishStateDbRead(db);
1515
1920
  }
1516
1921
  }
1517
1922
  /**
@@ -1537,21 +1942,31 @@ export function insightsSessionRows(dbPath, cutoff) {
1537
1942
  const col = (name) => (cols.has(name) ? `s.${name}` : 'NULL');
1538
1943
  const messageCols = tableColumns(db, 'messages');
1539
1944
  const hasMessages = messageCols.has('session_id') && messageCols.has('timestamp');
1540
- let indexed = false;
1541
- try {
1542
- indexed = hasMessages && db.prepare('PRAGMA index_list(messages)').all().some((r) => r.name === 'idx_messages_session');
1543
- }
1544
- catch {
1545
- indexed = false;
1546
- }
1945
+ const indexed = hasMessages && tableIndexes(db, 'messages').has('idx_messages_session');
1547
1946
  // Without the index a per-session lookup scans messages once per session; one grouped scan replaces it.
1548
- const lastMessage = !hasMessages ? 'NULL' : indexed ? '(SELECT MAX(m.timestamp) FROM messages m WHERE m.session_id = s.id)' : 'lm.last_message_at';
1549
- const join = hasMessages && !indexed ? 'LEFT JOIN (SELECT session_id, MAX(timestamp) AS last_message_at FROM messages GROUP BY session_id) lm ON lm.session_id = s.id' : '';
1947
+ const lastMessage = !hasMessages ? 'NULL' : indexed ? lastMessageTimestamp(indexed) : 'lm.last_message_at';
1948
+ const messageCte = hasMessages && !indexed ? 'latest_messages AS MATERIALIZED (SELECT session_id, MAX(timestamp) AS last_message_at FROM messages GROUP BY session_id)' : '';
1949
+ const join = messageCte ? 'LEFT JOIN latest_messages lm ON lm.session_id = s.id' : '';
1550
1950
  const source = cols.has('source') ? "LOWER(COALESCE(s.source, '')) != 'webui'" : '1';
1551
- const rows = db.prepare(`SELECT * FROM (SELECT s.id AS id, ${col('model')} AS model, ${col('message_count')} AS message_count, ${col('input_tokens')} AS input_tokens,
1951
+ const activityIndexed = cols.has('last_activity_at') && tableIndexes(db, 'sessions').has('idx_sessions_effective_activity');
1952
+ const activity = activityIndexed ? freshestActivity('s.last_activity_at', `MAX(COALESCE(${col('ended_at')}, 0), COALESCE(${lastMessage}, 0))`)
1953
+ : `MAX(COALESCE(s.started_at, 0), COALESCE(${col('ended_at')}, 0), COALESCE(${lastMessage}, 0))`;
1954
+ // The heartbeat can lag a message or end time; union their candidates before the exact activity check.
1955
+ const candidates = activityIndexed ? [
1956
+ 'SELECT s.id FROM sessions s INDEXED BY idx_sessions_effective_activity WHERE COALESCE(s.last_activity_at, s.started_at) >= ?',
1957
+ 'SELECT id FROM sessions WHERE started_at >= ?',
1958
+ ...(cols.has('ended_at') ? ['SELECT id FROM sessions WHERE ended_at >= ?'] : []),
1959
+ ...(hasMessages ? [indexed ? 'SELECT DISTINCT session_id AS id FROM messages WHERE timestamp >= ?' : 'SELECT session_id AS id FROM latest_messages WHERE last_message_at >= ?'] : []),
1960
+ ] : [];
1961
+ const ctes = [messageCte, ...(activityIndexed ? [`candidates AS MATERIALIZED (SELECT DISTINCT id FROM (${candidates.join(' UNION ALL ')}))`] : [])].filter(Boolean);
1962
+ const withClause = ctes.length ? `WITH ${ctes.join(', ')} ` : '';
1963
+ // CROSS JOIN keeps the candidate IDs ahead of the expensive per-session freshness projection.
1964
+ const from = activityIndexed ? 'candidates c CROSS JOIN sessions s ON s.id = c.id' : 'sessions s';
1965
+ const ordering = activityIndexed ? ' ORDER BY COALESCE(s.last_activity_at, s.started_at) DESC' : '';
1966
+ const rows = db.prepare(`${withClause}SELECT * FROM (SELECT s.id AS id, ${col('model')} AS model, ${col('message_count')} AS message_count, ${col('input_tokens')} AS input_tokens,
1552
1967
  ${col('output_tokens')} AS output_tokens, ${col('cache_read_tokens')} AS cache_read_tokens, ${col('estimated_cost_usd')} AS estimated_cost_usd, s.started_at AS started_at,
1553
- MAX(COALESCE(s.started_at, 0), COALESCE(${col('ended_at')}, 0), COALESCE(${lastMessage}, 0)) AS last_activity
1554
- FROM sessions s ${join} WHERE ${source}) WHERE last_activity >= ?`).all(cutoff);
1968
+ ${activity} AS last_activity
1969
+ FROM ${from} ${join} WHERE ${source}${ordering}) WHERE last_activity >= ?`).all(...Array(candidates.length + 1).fill(cutoff));
1555
1970
  return rows.map((r) => ({
1556
1971
  session_id: str(r.id), model: r.model, message_count: r.message_count, input_tokens: r.input_tokens, output_tokens: r.output_tokens,
1557
1972
  cache_read_tokens: r.cache_read_tokens, estimated_cost: r.estimated_cost_usd, created_at: r.started_at, updated_at: r.last_activity,
@@ -1561,7 +1976,7 @@ export function insightsSessionRows(dbPath, cutoff) {
1561
1976
  return [];
1562
1977
  }
1563
1978
  finally {
1564
- db.close();
1979
+ finishStateDbRead(db);
1565
1980
  }
1566
1981
  }
1567
1982
  /** A state.db `timestamp` in unix seconds: a number, or an ISO-8601 string (Python `fromisoformat`); null when unreadable. */
@@ -1590,7 +2005,7 @@ export function countConversationRounds(dbPath, sid, since = null) {
1590
2005
  rows = db.prepare('SELECT role, timestamp FROM messages WHERE session_id = ? ORDER BY timestamp ASC').all(sid);
1591
2006
  }
1592
2007
  finally {
1593
- db.close();
2008
+ finishStateDbRead(db);
1594
2009
  }
1595
2010
  }
1596
2011
  catch {
@@ -1631,7 +2046,7 @@ export function stateDbTailToolContent(dbPath, sid) {
1631
2046
  return typeof content === 'string' ? content : null;
1632
2047
  }
1633
2048
  finally {
1634
- db.close();
2049
+ finishStateDbRead(db);
1635
2050
  }
1636
2051
  }
1637
2052
  catch {