@maudecode/talaria-web 1.18.0 → 1.19.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 (567) hide show
  1. package/_release.json +8 -8
  2. package/dist/api/auth-raw.js +16 -16
  3. package/dist/api/auth-raw.js.map +1 -1
  4. package/dist/api/automation-raw.d.ts +2 -2
  5. package/dist/api/automation-raw.js +32 -32
  6. package/dist/api/automation-raw.js.map +1 -1
  7. package/dist/api/automation-router.d.ts +102 -22
  8. package/dist/api/automation-router.js +15 -13
  9. package/dist/api/automation-router.js.map +1 -1
  10. package/dist/api/chat-router.d.ts +1205 -34
  11. package/dist/api/chat-router.js +158 -78
  12. package/dist/api/chat-router.js.map +1 -1
  13. package/dist/api/git-router.d.ts +2 -2
  14. package/dist/api/git-router.js +29 -29
  15. package/dist/api/git-router.js.map +1 -1
  16. package/dist/api/raw-routes.d.ts +3 -3
  17. package/dist/api/raw-routes.js +63 -61
  18. package/dist/api/raw-routes.js.map +1 -1
  19. package/dist/api/router.d.ts +6 -6
  20. package/dist/api/router.js +22 -22
  21. package/dist/api/router.js.map +1 -1
  22. package/dist/api/session-visibility.d.ts +4 -4
  23. package/dist/api/session-visibility.js +26 -16
  24. package/dist/api/session-visibility.js.map +1 -1
  25. package/dist/api/sessions-router.d.ts +1928 -124
  26. package/dist/api/sessions-router.js +177 -173
  27. package/dist/api/sessions-router.js.map +1 -1
  28. package/dist/api/settings-router.d.ts +427 -9
  29. package/dist/api/settings-router.js +48 -32
  30. package/dist/api/settings-router.js.map +1 -1
  31. package/dist/api/sse-routes.d.ts +2 -2
  32. package/dist/api/sse-routes.js +63 -63
  33. package/dist/api/sse-routes.js.map +1 -1
  34. package/dist/api/tools-raw.d.ts +5 -0
  35. package/dist/api/tools-raw.js +58 -33
  36. package/dist/api/tools-raw.js.map +1 -1
  37. package/dist/api/tools-router.d.ts +663 -91
  38. package/dist/api/tools-router.js +309 -52
  39. package/dist/api/tools-router.js.map +1 -1
  40. package/dist/api/transcribe-stream.d.ts +16 -0
  41. package/dist/api/transcribe-stream.js +172 -0
  42. package/dist/api/transcribe-stream.js.map +1 -0
  43. package/dist/app.d.ts +4 -1
  44. package/dist/app.js +66 -47
  45. package/dist/app.js.map +1 -1
  46. package/dist/auth/gate.js +10 -9
  47. package/dist/auth/gate.js.map +1 -1
  48. package/dist/auth/passkeys.d.ts +15 -14
  49. package/dist/auth/passkeys.js +89 -91
  50. package/dist/auth/passkeys.js.map +1 -1
  51. package/dist/auth/store.d.ts +17 -7
  52. package/dist/auth/store.js +93 -52
  53. package/dist/auth/store.js.map +1 -1
  54. package/dist/bin/talaria-web.js +12 -81
  55. package/dist/bin/talaria-web.js.map +1 -1
  56. package/dist/cli/ctl.js +2 -1
  57. package/dist/cli/ctl.js.map +1 -1
  58. package/dist/cli/dotenv.d.ts +5 -0
  59. package/dist/cli/dotenv.js +14 -2
  60. package/dist/cli/dotenv.js.map +1 -1
  61. package/dist/cli/launcher.js +5 -5
  62. package/dist/cli/launcher.js.map +1 -1
  63. package/dist/cli/main.d.ts +1 -0
  64. package/dist/cli/main.js +85 -0
  65. package/dist/cli/main.js.map +1 -0
  66. package/dist/cli/supervise.d.ts +2 -0
  67. package/dist/cli/supervise.js +67 -4
  68. package/dist/cli/supervise.js.map +1 -1
  69. package/dist/config/agent-config.d.ts +34 -11
  70. package/dist/config/agent-config.js +113 -58
  71. package/dist/config/agent-config.js.map +1 -1
  72. package/dist/config.d.ts +2 -0
  73. package/dist/config.js +14 -5
  74. package/dist/config.js.map +1 -1
  75. package/dist/fs/atomic.d.ts +1 -0
  76. package/dist/fs/atomic.js +4 -1
  77. package/dist/fs/atomic.js.map +1 -1
  78. package/dist/fs/stat.d.ts +1 -0
  79. package/dist/fs/stat.js +11 -0
  80. package/dist/fs/stat.js.map +1 -0
  81. package/dist/http/context.d.ts +14 -9
  82. package/dist/http/context.js +29 -20
  83. package/dist/http/context.js.map +1 -1
  84. package/dist/onboarding.d.ts +2 -2
  85. package/dist/onboarding.js +10 -8
  86. package/dist/onboarding.js.map +1 -1
  87. package/dist/profiles/profiles.d.ts +10 -4
  88. package/dist/profiles/profiles.js +77 -30
  89. package/dist/profiles/profiles.js.map +1 -1
  90. package/dist/projects.d.ts +15 -6
  91. package/dist/projects.js +55 -47
  92. package/dist/projects.js.map +1 -1
  93. package/dist/providers/catalog.d.ts +78 -12
  94. package/dist/providers/catalog.js +445 -141
  95. package/dist/providers/catalog.js.map +1 -1
  96. package/dist/providers/env-file.d.ts +10 -3
  97. package/dist/providers/env-file.js +24 -75
  98. package/dist/providers/env-file.js.map +1 -1
  99. package/dist/providers/runtime-env.d.ts +4 -2
  100. package/dist/providers/runtime-env.js +9 -6
  101. package/dist/providers/runtime-env.js.map +1 -1
  102. package/dist/providers/tables.d.ts +3 -1
  103. package/dist/providers/tables.js +4 -2
  104. package/dist/providers/tables.js.map +1 -1
  105. package/dist/redact.js +18 -9
  106. package/dist/redact.js.map +1 -1
  107. package/dist/release.d.ts +7 -0
  108. package/dist/release.js +14 -0
  109. package/dist/release.js.map +1 -1
  110. package/dist/runtime.d.ts +5 -1
  111. package/dist/runtime.js +233 -90
  112. package/dist/runtime.js.map +1 -1
  113. package/dist/server.js +10 -0
  114. package/dist/server.js.map +1 -1
  115. package/dist/sessions/anchor.d.ts +9 -3
  116. package/dist/sessions/anchor.js +28 -36
  117. package/dist/sessions/anchor.js.map +1 -1
  118. package/dist/sessions/background-events.d.ts +2 -0
  119. package/dist/sessions/background-events.js +10 -0
  120. package/dist/sessions/background-events.js.map +1 -0
  121. package/dist/sessions/background-tasks.d.ts +26 -20
  122. package/dist/sessions/background-tasks.js +88 -86
  123. package/dist/sessions/background-tasks.js.map +1 -1
  124. package/dist/sessions/background-updates.d.ts +15 -8
  125. package/dist/sessions/background-updates.js +101 -25
  126. package/dist/sessions/background-updates.js.map +1 -1
  127. package/dist/sessions/cli-sessions.d.ts +15 -5
  128. package/dist/sessions/cli-sessions.js +62 -43
  129. package/dist/sessions/cli-sessions.js.map +1 -1
  130. package/dist/sessions/completions.d.ts +30 -2
  131. package/dist/sessions/completions.js +294 -53
  132. package/dist/sessions/completions.js.map +1 -1
  133. package/dist/sessions/compress.d.ts +8 -0
  134. package/dist/sessions/compress.js +2 -2
  135. package/dist/sessions/compress.js.map +1 -1
  136. package/dist/sessions/drafts.d.ts +14 -10
  137. package/dist/sessions/drafts.js +38 -43
  138. package/dist/sessions/drafts.js.map +1 -1
  139. package/dist/sessions/gateway-sessions.d.ts +46 -0
  140. package/dist/sessions/gateway-sessions.js +378 -0
  141. package/dist/sessions/gateway-sessions.js.map +1 -0
  142. package/dist/sessions/goals.d.ts +31 -0
  143. package/dist/sessions/goals.js +75 -0
  144. package/dist/sessions/goals.js.map +1 -0
  145. package/dist/sessions/journal.d.ts +5 -79
  146. package/dist/sessions/journal.js +15 -606
  147. package/dist/sessions/journal.js.map +1 -1
  148. package/dist/sessions/list.d.ts +27 -18
  149. package/dist/sessions/list.js +133 -131
  150. package/dist/sessions/list.js.map +1 -1
  151. package/dist/sessions/merge.d.ts +68 -10
  152. package/dist/sessions/merge.js +259 -51
  153. package/dist/sessions/merge.js.map +1 -1
  154. package/dist/sessions/notices.d.ts +22 -0
  155. package/dist/sessions/notices.js +72 -0
  156. package/dist/sessions/notices.js.map +1 -0
  157. package/dist/sessions/pending.d.ts +10 -4
  158. package/dist/sessions/pending.js +83 -7
  159. package/dist/sessions/pending.js.map +1 -1
  160. package/dist/sessions/prefix-index.d.ts +82 -0
  161. package/dist/sessions/prefix-index.js +232 -0
  162. package/dist/sessions/prefix-index.js.map +1 -0
  163. package/dist/sessions/recovery.d.ts +4 -7
  164. package/dist/sessions/recovery.js +97 -281
  165. package/dist/sessions/recovery.js.map +1 -1
  166. package/dist/sessions/relay.d.ts +20 -20
  167. package/dist/sessions/relay.js +205 -127
  168. package/dist/sessions/relay.js.map +1 -1
  169. package/dist/sessions/service.d.ts +162 -59
  170. package/dist/sessions/service.js +1133 -521
  171. package/dist/sessions/service.js.map +1 -1
  172. package/dist/sessions/session.d.ts +18 -2
  173. package/dist/sessions/session.js +29 -5
  174. package/dist/sessions/session.js.map +1 -1
  175. package/dist/sessions/shares.d.ts +7 -6
  176. package/dist/sessions/shares.js +22 -45
  177. package/dist/sessions/shares.js.map +1 -1
  178. package/dist/sessions/source-kind.js +1 -0
  179. package/dist/sessions/source-kind.js.map +1 -1
  180. package/dist/sessions/state-db-identity.d.ts +18 -0
  181. package/dist/sessions/state-db-identity.js +67 -0
  182. package/dist/sessions/state-db-identity.js.map +1 -0
  183. package/dist/sessions/state-db-worker.js +3 -1
  184. package/dist/sessions/state-db-worker.js.map +1 -1
  185. package/dist/sessions/state-db.d.ts +53 -8
  186. package/dist/sessions/state-db.js +576 -50
  187. package/dist/sessions/state-db.js.map +1 -1
  188. package/dist/sessions/storage/backend.d.ts +162 -0
  189. package/dist/sessions/storage/backend.js +19 -0
  190. package/dist/sessions/storage/backend.js.map +1 -0
  191. package/dist/sessions/storage/conformance.d.ts +20 -0
  192. package/dist/sessions/storage/conformance.js +237 -0
  193. package/dist/sessions/storage/conformance.js.map +1 -0
  194. package/dist/sessions/storage/document.d.ts +16 -0
  195. package/dist/sessions/storage/document.js +44 -0
  196. package/dist/sessions/storage/document.js.map +1 -0
  197. package/dist/sessions/storage/json-documents.d.ts +23 -0
  198. package/dist/sessions/storage/json-documents.js +233 -0
  199. package/dist/sessions/storage/json-documents.js.map +1 -0
  200. package/dist/sessions/storage/json-files.d.ts +12 -0
  201. package/dist/sessions/storage/json-files.js +124 -0
  202. package/dist/sessions/storage/json-files.js.map +1 -0
  203. package/dist/sessions/storage/json-installation.d.ts +3 -0
  204. package/dist/sessions/storage/json-installation.js +31 -0
  205. package/dist/sessions/storage/json-installation.js.map +1 -0
  206. package/dist/sessions/storage/json-journal.d.ts +114 -0
  207. package/dist/sessions/storage/json-journal.js +686 -0
  208. package/dist/sessions/storage/json-journal.js.map +1 -0
  209. package/dist/sessions/storage/json-recovery.d.ts +4 -0
  210. package/dist/sessions/storage/json-recovery.js +195 -0
  211. package/dist/sessions/storage/json-recovery.js.map +1 -0
  212. package/dist/sessions/storage/json.d.ts +68 -0
  213. package/dist/sessions/storage/json.js +258 -0
  214. package/dist/sessions/storage/json.js.map +1 -0
  215. package/dist/sessions/storage/row.d.ts +6 -0
  216. package/dist/sessions/storage/row.js +49 -0
  217. package/dist/sessions/storage/row.js.map +1 -0
  218. package/dist/sessions/storage/search-text.d.ts +4 -0
  219. package/dist/sessions/storage/search-text.js +16 -0
  220. package/dist/sessions/storage/search-text.js.map +1 -0
  221. package/dist/sessions/store.d.ts +63 -73
  222. package/dist/sessions/store.js +305 -611
  223. package/dist/sessions/store.js.map +1 -1
  224. package/dist/sessions/streams.d.ts +4 -0
  225. package/dist/sessions/streams.js +9 -1
  226. package/dist/sessions/streams.js.map +1 -1
  227. package/dist/sessions/titles.js +1 -1
  228. package/dist/sessions/titles.js.map +1 -1
  229. package/dist/sessions/todo.d.ts +5 -0
  230. package/dist/sessions/todo.js +28 -21
  231. package/dist/sessions/todo.js.map +1 -1
  232. package/dist/sessions/tool-display.d.ts +5 -4
  233. package/dist/sessions/tool-display.js +22 -16
  234. package/dist/sessions/tool-display.js.map +1 -1
  235. package/dist/sessions/turn-context.d.ts +10 -1
  236. package/dist/sessions/turn-context.js +7 -4
  237. package/dist/sessions/turn-context.js.map +1 -1
  238. package/dist/sessions/turn.d.ts +87 -17
  239. package/dist/sessions/turn.js +795 -347
  240. package/dist/sessions/turn.js.map +1 -1
  241. package/dist/sessions/wakeup-pause.d.ts +8 -13
  242. package/dist/sessions/wakeup-pause.js +31 -49
  243. package/dist/sessions/wakeup-pause.js.map +1 -1
  244. package/dist/sessions/window.js +13 -12
  245. package/dist/sessions/window.js.map +1 -1
  246. package/dist/settings.d.ts +19 -7
  247. package/dist/settings.js +179 -183
  248. package/dist/settings.js.map +1 -1
  249. package/dist/sidecar/client.d.ts +17 -3
  250. package/dist/sidecar/client.js +78 -8
  251. package/dist/sidecar/client.js.map +1 -1
  252. package/dist/sidecar/discover.d.ts +2 -1
  253. package/dist/sidecar/discover.js +93 -10
  254. package/dist/sidecar/discover.js.map +1 -1
  255. package/dist/sidecar/env.d.ts +4 -0
  256. package/dist/sidecar/env.js +3 -0
  257. package/dist/sidecar/env.js.map +1 -0
  258. package/dist/sidecar/fake.js +19 -2
  259. package/dist/sidecar/fake.js.map +1 -1
  260. package/dist/test/env-file.d.ts +5 -0
  261. package/dist/test/env-file.js +88 -0
  262. package/dist/test/env-file.js.map +1 -0
  263. package/dist/test/harness.d.ts +1 -0
  264. package/dist/test/harness.js +1 -1
  265. package/dist/test/harness.js.map +1 -1
  266. package/dist/test/passkeys.d.ts +22 -0
  267. package/dist/test/passkeys.js +62 -0
  268. package/dist/test/passkeys.js.map +1 -0
  269. package/dist/test/storage.d.ts +15 -0
  270. package/dist/test/storage.js +26 -0
  271. package/dist/test/storage.js.map +1 -0
  272. package/dist/tools/commands.d.ts +6 -1
  273. package/dist/tools/commands.js +8 -2
  274. package/dist/tools/commands.js.map +1 -1
  275. package/dist/tools/connectors.d.ts +33 -0
  276. package/dist/tools/connectors.js +121 -0
  277. package/dist/tools/connectors.js.map +1 -0
  278. package/dist/tools/crons.d.ts +38 -1
  279. package/dist/tools/crons.js +82 -15
  280. package/dist/tools/crons.js.map +1 -1
  281. package/dist/tools/dashboard-plugins.d.ts +5 -5
  282. package/dist/tools/dashboard-plugins.js +14 -14
  283. package/dist/tools/dashboard-plugins.js.map +1 -1
  284. package/dist/tools/extensions.d.ts +5 -4
  285. package/dist/tools/extensions.js +213 -207
  286. package/dist/tools/extensions.js.map +1 -1
  287. package/dist/tools/gateway-restart.d.ts +62 -0
  288. package/dist/tools/gateway-restart.js +142 -0
  289. package/dist/tools/gateway-restart.js.map +1 -0
  290. package/dist/tools/health.d.ts +15 -3
  291. package/dist/tools/health.js +97 -34
  292. package/dist/tools/health.js.map +1 -1
  293. package/dist/tools/hygiene.d.ts +4 -3
  294. package/dist/tools/hygiene.js +23 -9
  295. package/dist/tools/hygiene.js.map +1 -1
  296. package/dist/tools/kanban.d.ts +4 -2
  297. package/dist/tools/kanban.js +28 -19
  298. package/dist/tools/kanban.js.map +1 -1
  299. package/dist/tools/memory-masks.d.ts +5 -0
  300. package/dist/tools/memory-masks.js +256 -0
  301. package/dist/tools/memory-masks.js.map +1 -0
  302. package/dist/tools/memory.d.ts +29 -3
  303. package/dist/tools/memory.js +69 -27
  304. package/dist/tools/memory.js.map +1 -1
  305. package/dist/tools/plugin-settings.d.ts +15 -0
  306. package/dist/tools/plugin-settings.js +90 -0
  307. package/dist/tools/plugin-settings.js.map +1 -0
  308. package/dist/tools/skills.d.ts +9 -4
  309. package/dist/tools/skills.js +44 -13
  310. package/dist/tools/skills.js.map +1 -1
  311. package/dist/tools/update-notifications.d.ts +34 -15
  312. package/dist/tools/update-notifications.js +141 -57
  313. package/dist/tools/update-notifications.js.map +1 -1
  314. package/dist/tools/update-rollback.d.ts +11 -0
  315. package/dist/tools/update-rollback.js +28 -0
  316. package/dist/tools/update-rollback.js.map +1 -0
  317. package/dist/tools/updates.d.ts +93 -33
  318. package/dist/tools/updates.js +1051 -345
  319. package/dist/tools/updates.js.map +1 -1
  320. package/dist/util.d.ts +2 -0
  321. package/dist/util.js +5 -0
  322. package/dist/util.js.map +1 -1
  323. package/dist/workspace/git.d.ts +16 -1
  324. package/dist/workspace/git.js +291 -11
  325. package/dist/workspace/git.js.map +1 -1
  326. package/dist/workspace/media.d.ts +12 -9
  327. package/dist/workspace/media.js +31 -20
  328. package/dist/workspace/media.js.map +1 -1
  329. package/dist/workspace/rollback.d.ts +2 -2
  330. package/dist/workspace/rollback.js +5 -5
  331. package/dist/workspace/rollback.js.map +1 -1
  332. package/dist/workspace/workspaces.d.ts +15 -19
  333. package/dist/workspace/workspaces.js +55 -127
  334. package/dist/workspace/workspaces.js.map +1 -1
  335. package/dist/workspace/worktrees.js +170 -32
  336. package/dist/workspace/worktrees.js.map +1 -1
  337. package/package.json +5 -3
  338. package/sidecar/agent_dependency.json +4 -4
  339. package/sidecar/agent_support.json +26 -0
  340. package/sidecar/scripts/record-fixtures.py +12 -3
  341. package/sidecar/scripts/replay_sidecar.py +8 -0
  342. package/sidecar/scripts/suite_runner.py +302 -0
  343. package/sidecar/scripts/test.sh +12 -4
  344. package/sidecar/talaria_sidecar/__init__.py +6 -1
  345. package/sidecar/talaria_sidecar/__main__.py +4 -2
  346. package/sidecar/talaria_sidecar/agent_mutation.py +975 -0
  347. package/sidecar/talaria_sidecar/agent_services.py +1053 -0
  348. package/sidecar/talaria_sidecar/dependency_sync.py +85 -0
  349. package/sidecar/talaria_sidecar/home.py +43 -1
  350. package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
  351. package/sidecar/talaria_sidecar/methods/aux.py +139 -31
  352. package/sidecar/talaria_sidecar/methods/chat.py +847 -134
  353. package/sidecar/talaria_sidecar/methods/commands.py +213 -4
  354. package/sidecar/talaria_sidecar/methods/config.py +123 -4
  355. package/sidecar/talaria_sidecar/methods/connectors.py +343 -0
  356. package/sidecar/talaria_sidecar/methods/cron.py +366 -34
  357. package/sidecar/talaria_sidecar/methods/cron_execution.py +284 -0
  358. package/sidecar/talaria_sidecar/methods/gateway.py +194 -54
  359. package/sidecar/talaria_sidecar/methods/gateway_sessions.py +234 -0
  360. package/sidecar/talaria_sidecar/methods/goals.py +81 -41
  361. package/sidecar/talaria_sidecar/methods/kanban.py +80 -27
  362. package/sidecar/talaria_sidecar/methods/memory.py +84 -0
  363. package/sidecar/talaria_sidecar/methods/oauth.py +5 -2
  364. package/sidecar/talaria_sidecar/methods/process.py +160 -11
  365. package/sidecar/talaria_sidecar/methods/profiles.py +44 -6
  366. package/sidecar/talaria_sidecar/methods/providers.py +196 -26
  367. package/sidecar/talaria_sidecar/methods/runtime.py +69 -1
  368. package/sidecar/talaria_sidecar/methods/skills.py +67 -4
  369. package/sidecar/talaria_sidecar/methods/state_db.py +277 -70
  370. package/sidecar/talaria_sidecar/methods/stt.py +159 -78
  371. package/sidecar/talaria_sidecar/methods/text.py +14 -1
  372. package/sidecar/talaria_sidecar/methods/toolsets.py +49 -0
  373. package/sidecar/talaria_sidecar/methods/usage.py +113 -14
  374. package/sidecar/talaria_sidecar/methods/worktree.py +26 -19
  375. package/sidecar/talaria_sidecar/methods/write_approval.py +181 -0
  376. package/sidecar/talaria_sidecar/rpc.py +45 -4
  377. package/sidecar/talaria_sidecar/runtime.py +81 -12
  378. package/sidecar/talaria_sidecar/speed.py +35 -0
  379. package/static/dist/FILES.txt +126 -127
  380. package/static/dist/assets/AppShell-cey33v9s.js +8 -0
  381. package/static/dist/assets/ChatPage-i8wnugr7.js +18 -0
  382. package/static/dist/assets/ConnectTargets-hg19553x.js +1 -0
  383. package/static/dist/assets/ExtensionRoute-cly4apm5.js +1 -0
  384. package/static/dist/assets/Field-j8uqoigz.js +9 -0
  385. package/static/dist/assets/HubRoute-e4bgz41j.js +2 -0
  386. package/static/dist/assets/Markdown-lahuichm.js +598 -0
  387. package/static/dist/assets/RightPanel-lbo4i0fn.js +1 -0
  388. package/static/dist/assets/Select-gqatm2e0.js +1 -0
  389. package/static/dist/assets/SessionListPanel-mfqayfjh.js +1 -0
  390. package/static/dist/assets/SettingsLayout-b8980u7k.js +1 -0
  391. package/static/dist/assets/States-e7dkgtxf.js +1 -0
  392. package/static/dist/assets/Toaster-bfqqy786.js +1 -0
  393. package/static/dist/assets/_app-n1n6pfx1.js +1 -0
  394. package/static/dist/assets/{_app.ext._extensionId-gfbzpoou.js → _app.ext._extensionId-buxjxav3.js} +1 -1
  395. package/static/dist/assets/_app.index-jt8zwiu2.js +1 -0
  396. package/static/dist/assets/_app.insights-cv76ml71.js +1 -0
  397. package/static/dist/assets/_app.kanban-h6ts53j7.js +1 -0
  398. package/static/dist/assets/_app.logs-dm873z90.js +1 -0
  399. package/static/dist/assets/_app.memory-glk5y6m8.js +1 -0
  400. package/static/dist/assets/_app.profiles-ka6zpstq.js +1 -0
  401. package/static/dist/assets/{_app.session._sessionId-fn1bhde6.js → _app.session._sessionId-g2vzghvc.js} +1 -1
  402. package/static/dist/assets/_app.settings-hgjdhzcd.js +1 -0
  403. package/static/dist/assets/_app.settings._section-jf2kmhae.js +3 -0
  404. package/static/dist/assets/_app.skills-cii852yq.js +1 -0
  405. package/static/dist/assets/_app.tasks-fp9q7atm.js +1 -0
  406. package/static/dist/assets/_app.todos-dw94qzue.js +1 -0
  407. package/static/dist/assets/_app.workspaces-inyygzze.js +1 -0
  408. package/static/dist/assets/{abnfDiagram-VCTEODGH-c8kdixke.js → abnfDiagram-VCTEODGH-gjayq3ra.js} +1 -1
  409. package/static/dist/assets/architecture-7GRP2DOG-lp56972r.js +1 -0
  410. package/static/dist/assets/{architectureDiagram-5GKGNRK7-cku06x1p.js → architectureDiagram-5GKGNRK7-nwb3xovn.js} +1 -1
  411. package/static/dist/assets/archive-b1j004vn.js +1 -0
  412. package/static/dist/assets/back-g74gkozu.js +1 -0
  413. package/static/dist/assets/{blockDiagram-I7D4REHJ-jzthp1wa.js → blockDiagram-I7D4REHJ-joedghi3.js} +1 -1
  414. package/static/dist/assets/{c4Diagram-7LVT6UL2-mwr67mt8.js → c4Diagram-7LVT6UL2-hyrjrohe.js} +1 -1
  415. package/static/dist/assets/channel-es95o4y8.js +1 -0
  416. package/static/dist/assets/{chunk-4HAMMTFA-gpq1y228.js → chunk-4HAMMTFA-koouqbut.js} +1 -1
  417. package/static/dist/assets/{chunk-75Z2AOVW-ktje58c4.js → chunk-75Z2AOVW-bl4bhdnt.js} +1 -1
  418. package/static/dist/assets/{chunk-DU6HZSFF-ofajdixh.js → chunk-DU6HZSFF-gpedttsp.js} +3 -3
  419. package/static/dist/assets/{chunk-F27PBJKO-cmev85ti.js → chunk-F27PBJKO-d8xxi0zi.js} +1 -1
  420. package/static/dist/assets/{chunk-GMAD6QVW-htjgt22y.js → chunk-GMAD6QVW-jro0hl73.js} +1 -1
  421. package/static/dist/assets/{chunk-GVQU2GXP-qndvoxl7.js → chunk-GVQU2GXP-djn6gbyu.js} +1 -1
  422. package/static/dist/assets/{chunk-IMKFNOWR-e5bkd5q6.js → chunk-IMKFNOWR-vw4p5p7o.js} +1 -1
  423. package/static/dist/assets/{chunk-L3NEJ4N5-mj1155r6.js → chunk-L3NEJ4N5-n1hj77q8.js} +1 -1
  424. package/static/dist/assets/{chunk-OSK3NFVY-e7gkikcw.js → chunk-OSK3NFVY-oy0gwn2n.js} +1 -1
  425. package/static/dist/assets/{chunk-P2QGCYS3-f7t54xfm.js → chunk-P2QGCYS3-onf1hz6c.js} +1 -1
  426. package/static/dist/assets/{chunk-POPQ4Y6H-i927v9oa.js → chunk-POPQ4Y6H-m8sqn7cs.js} +1 -1
  427. package/static/dist/assets/{chunk-PWAF6VOD-m84lmnyt.js → chunk-PWAF6VOD-ek4smc8x.js} +1 -1
  428. package/static/dist/assets/{chunk-SHT3W25Y-mi0tlcu9.js → chunk-SHT3W25Y-g9gfvefo.js} +3 -3
  429. package/static/dist/assets/{chunk-SVP7TREG-dnau702j.js → chunk-SVP7TREG-f9nfv2cu.js} +1 -1
  430. package/static/dist/assets/{chunk-TICWLB2K-edem1gkg.js → chunk-TICWLB2K-bnk3m1mq.js} +1 -1
  431. package/static/dist/assets/classDiagram-ZZMXUADV-kmihoqxh.js +1 -0
  432. package/static/dist/assets/classDiagram-v2-VYDZK3BY-kmihoqxh.js +1 -0
  433. package/static/dist/assets/copied-bli9tbcx.js +1 -0
  434. package/static/dist/assets/{copy-magtofyp.js → copy-jamtd1pf.js} +1 -1
  435. package/static/dist/assets/{cynefin-OW5HDTMX-siqlulkl.js → cynefin-OW5HDTMX-f8xfdd3f.js} +1 -1
  436. package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-i648tah9.js → cynefinDiagram-5FMLGOSQ-n9lb99h9.js} +1 -1
  437. package/static/dist/assets/{dagre-GXQ25YYZ-mwn6uc17.js → dagre-GXQ25YYZ-dgailddi.js} +4 -4
  438. package/static/dist/assets/{diagram-S7CK7UJ4-l8abxaes.js → diagram-S7CK7UJ4-m6aeoe7c.js} +1 -1
  439. package/static/dist/assets/{diagram-UQ7AKVKN-mekq7zgq.js → diagram-UQ7AKVKN-jall3j0x.js} +1 -1
  440. package/static/dist/assets/{diagram-VSXAHHWV-fnky6lvc.js → diagram-VSXAHHWV-hbawi7hn.js} +1 -1
  441. package/static/dist/assets/{diagram-VX7I27RA-xe6r5qfw.js → diagram-VX7I27RA-hendl2tt.js} +1 -1
  442. package/static/dist/assets/{diagram-Z3DM3KII-nq07dg7k.js → diagram-Z3DM3KII-igwc35p3.js} +1 -1
  443. package/static/dist/assets/{ebnfDiagram-PWID7BFC-c62ipm3g.js → ebnfDiagram-PWID7BFC-c64knz08.js} +1 -1
  444. package/static/dist/assets/{erDiagram-RLTQ6QDP-h39kmnzy.js → erDiagram-RLTQ6QDP-brdu9lcm.js} +1 -1
  445. package/static/dist/assets/eventmodeling-NTZA5JFV-njk1j8wl.js +1 -0
  446. package/static/dist/assets/flowDiagram-HODETNUW-nma6f0ga.js +1 -0
  447. package/static/dist/assets/folder-plus-lvt9kudb.js +1 -0
  448. package/static/dist/assets/{ganttDiagram-EL5Y4UJY-ngbsyp37.js → ganttDiagram-EL5Y4UJY-ma6jxj5i.js} +1 -1
  449. package/static/dist/assets/{gitGraph-4MIJSDKK-fr2i7caa.js → gitGraph-4MIJSDKK-naa9w6xa.js} +1 -1
  450. package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-eravdti4.js → gitGraphDiagram-WWUBYQGX-hzltvsod.js} +1 -1
  451. package/static/dist/assets/{highlighted-body-KPVGNVTW-jwpop9p8.js → highlighted-body-KPVGNVTW-ghoavj8d.js} +1 -1
  452. package/static/dist/assets/host-hzm8n85n.js +1 -0
  453. package/static/dist/assets/index-drdcaqeq.css +2 -0
  454. package/static/dist/assets/index-jzm844he.js +11 -0
  455. package/static/dist/assets/{info-A6RAGUB7-h11x0dxo.js → info-A6RAGUB7-mn2ut5ko.js} +1 -1
  456. package/static/dist/assets/{infoDiagram-27XIBGKW-dfh47dw9.js → infoDiagram-27XIBGKW-h6kuqvsb.js} +1 -1
  457. package/static/dist/assets/{ishikawaDiagram-5VMMS53U-i6ypq351.js → ishikawaDiagram-5VMMS53U-e7bnwq7h.js} +1 -1
  458. package/static/dist/assets/{journeyDiagram-3NMN7TZE-lduf1z95.js → journeyDiagram-3NMN7TZE-etd1bi6p.js} +1 -1
  459. package/static/dist/assets/{kanban-definition-UXKFOSKX-e18bu4pg.js → kanban-definition-UXKFOSKX-emb1g9dz.js} +4 -4
  460. package/static/dist/assets/lazyRouteComponent-o816ytzl.js +1 -0
  461. package/static/dist/assets/{line-meizqsvn.js → line-h0ojzsvs.js} +1 -1
  462. package/static/dist/assets/link-k01ryaf9.js +1 -0
  463. package/static/dist/assets/login-bsae49ew.js +1 -0
  464. package/static/dist/assets/mermaid-HWGCJPDP-ihl34s74.js +1 -0
  465. package/static/dist/assets/{mermaid-parser.core-ntne45i9.js → mermaid-parser.core-bnre27u8.js} +3 -3
  466. package/static/dist/assets/{mindmap-definition-YA3MSWOX-27b26eok.js → mindmap-definition-YA3MSWOX-o1vathns.js} +1 -1
  467. package/static/dist/assets/onboarding-c65tysya.js +1 -0
  468. package/static/dist/assets/onboarding_model_label-gpn28uix.js +1 -0
  469. package/static/dist/assets/{packet-AYTQ26CC-l6jdka8p.js → packet-AYTQ26CC-hatq7m2e.js} +1 -1
  470. package/static/dist/assets/pcmTap.worklet-74EKjWAZ.js +1 -0
  471. package/static/dist/assets/{pegDiagram-XKGWAZYB-g1er3bxj.js → pegDiagram-XKGWAZYB-gwisz8jv.js} +1 -1
  472. package/static/dist/assets/{pie-WAS4IAKB-i0ylcrt9.js → pie-WAS4IAKB-fpftf0mk.js} +1 -1
  473. package/static/dist/assets/{pieDiagram-E7YTZNPT-fbz1e55z.js → pieDiagram-E7YTZNPT-khm1o9a6.js} +1 -1
  474. package/static/dist/assets/{quadrantDiagram-AXDQQJYC-hhue7d8o.js → quadrantDiagram-AXDQQJYC-owqc4uqn.js} +1 -1
  475. package/static/dist/assets/{radar-RG4KPBEZ-f9b70aa4.js → radar-RG4KPBEZ-cntfwjpq.js} +1 -1
  476. package/static/dist/assets/{railroad-74A4TZTK-lgsuoeda.js → railroad-74A4TZTK-a7t5eibp.js} +1 -1
  477. package/static/dist/assets/railroad-abnf-HS5TGJTU-ol8cdktf.js +1 -0
  478. package/static/dist/assets/railroad-ebnf-LZEXJU2U-i8vzd429.js +1 -0
  479. package/static/dist/assets/railroad-peg-WCYAUIDC-nw3ulewz.js +1 -0
  480. package/static/dist/assets/{railroadDiagram-O6MQD6OU-eqpy9fnz.js → railroadDiagram-O6MQD6OU-etuh0qa0.js} +1 -1
  481. package/static/dist/assets/{refresh-cw-hlwb02qv.js → refresh-cw-mqbkv2a3.js} +1 -1
  482. package/static/dist/assets/refresh-l29ljp76.js +1 -0
  483. package/static/dist/assets/{requirementDiagram-BXWQKSXE-ogf6puhi.js → requirementDiagram-BXWQKSXE-oq2ulk22.js} +1 -1
  484. package/static/dist/assets/{sankeyDiagram-P5KCCOFB-fqb8xcjf.js → sankeyDiagram-P5KCCOFB-iqc02emv.js} +1 -1
  485. package/static/dist/assets/saved-ndd4t2xb.js +1 -0
  486. package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-dlcz6qjz.js → sequenceDiagram-WJ2MYXX4-i0v116q0.js} +2 -2
  487. package/static/dist/assets/settings_section_extensions_title-jhfo6o8z.js +1 -0
  488. package/static/dist/assets/share._token-c1ri0hy1.js +2 -0
  489. package/static/dist/assets/sliders-horizontal-j3jjt9tv.js +1 -0
  490. package/static/dist/assets/{stateDiagram-D77RDMKH-fyoznpcs.js → stateDiagram-D77RDMKH-dnpt1ato.js} +1 -1
  491. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-bxjacpk6.js +1 -0
  492. package/static/dist/assets/{swimlanes-42K2YHIH-oefldhfy.js → swimlanes-42K2YHIH-loqbdazb.js} +1 -1
  493. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-id62p9hz.js +8 -0
  494. package/static/dist/assets/{timeline-definition-24CTP7MA-b1vl2zil.js → timeline-definition-24CTP7MA-ol2hnvma.js} +1 -1
  495. package/static/dist/assets/toast-i3h34v6o.js +64 -0
  496. package/static/dist/assets/{treeView-Q6P3EWNA-f7hcci5o.js → treeView-Q6P3EWNA-idrj6iox.js} +1 -1
  497. package/static/dist/assets/{treemap-WGGIJYW6-nl85071a.js → treemap-WGGIJYW6-lzr49vv5.js} +1 -1
  498. package/static/dist/assets/untitled-oaoya4qj.js +1 -0
  499. package/static/dist/assets/useForm-nfaxgmsk.js +1 -0
  500. package/static/dist/assets/{useParams-b4dq6n4q.js → useParams-gjc70qru.js} +1 -1
  501. package/static/dist/assets/{useSearch-ipfmv3he.js → useSearch-e6ae539u.js} +1 -1
  502. package/static/dist/assets/{vennDiagram-4TSXK5OY-h5hzbk0n.js → vennDiagram-4TSXK5OY-bvwr9o2z.js} +1 -1
  503. package/static/dist/assets/{wardley-WFR3VGLG-jftvpm3e.js → wardley-WFR3VGLG-31ui8tma.js} +1 -1
  504. package/static/dist/assets/{wardleyDiagram-VM6X3IG4-hedh85ge.js → wardleyDiagram-VM6X3IG4-k5wqtx4b.js} +1 -1
  505. package/static/dist/assets/{xychartDiagram-S5SC5T6Z-e49523ab.js → xychartDiagram-S5SC5T6Z-aqbgvujg.js} +1 -1
  506. package/static/dist/index.html +1 -1
  507. package/static/dist/sw.js +1 -1
  508. package/static/dist/assets/AppShell-g3bkgnvt.js +0 -1
  509. package/static/dist/assets/ChatPage-oo7bdan6.js +0 -25
  510. package/static/dist/assets/Dialog-g355qxhp.js +0 -1
  511. package/static/dist/assets/ExtensionRoute-i2y9fvjm.js +0 -1
  512. package/static/dist/assets/HubRoute-lmkrbw47.js +0 -2
  513. package/static/dist/assets/Markdown-j4kut4j2.js +0 -598
  514. package/static/dist/assets/RightPanel-ohlsdzfl.js +0 -1
  515. package/static/dist/assets/Select-kddq75qc.js +0 -1
  516. package/static/dist/assets/SessionListPanel-lu3oy0hw.js +0 -1
  517. package/static/dist/assets/SettingsLayout-o9bv4mgb.js +0 -1
  518. package/static/dist/assets/States-eq6fe29m.js +0 -1
  519. package/static/dist/assets/Toaster-pc247u9f.js +0 -1
  520. package/static/dist/assets/_app-f8n1i0m1.js +0 -1
  521. package/static/dist/assets/_app.index-bzzv80hk.js +0 -1
  522. package/static/dist/assets/_app.insights-dzixbryi.js +0 -1
  523. package/static/dist/assets/_app.kanban-c6hep2us.js +0 -1
  524. package/static/dist/assets/_app.logs-i69qds98.js +0 -1
  525. package/static/dist/assets/_app.memory-kl4vf0gd.js +0 -1
  526. package/static/dist/assets/_app.profiles-b8zinmyp.js +0 -1
  527. package/static/dist/assets/_app.settings-cg8plwmf.js +0 -1
  528. package/static/dist/assets/_app.settings._section-bkwbi9zn.js +0 -3
  529. package/static/dist/assets/_app.skills-cs47sbvs.js +0 -1
  530. package/static/dist/assets/_app.tasks-dwvk64tc.js +0 -1
  531. package/static/dist/assets/_app.todos-c8xjm8p3.js +0 -1
  532. package/static/dist/assets/_app.workspaces-ehvd1gds.js +0 -1
  533. package/static/dist/assets/architecture-7GRP2DOG-6crvnylg.js +0 -1
  534. package/static/dist/assets/back-h7aun7ew.js +0 -1
  535. package/static/dist/assets/cancel-fhi0vcnu.js +0 -1
  536. package/static/dist/assets/channel-b2zo73z1.js +0 -1
  537. package/static/dist/assets/classDiagram-ZZMXUADV-oe99st8x.js +0 -1
  538. package/static/dist/assets/classDiagram-v2-VYDZK3BY-oe99st8x.js +0 -1
  539. package/static/dist/assets/copied-hcplv46p.js +0 -1
  540. package/static/dist/assets/endpoints-klbxod4t.js +0 -64
  541. package/static/dist/assets/eventmodeling-NTZA5JFV-obfede4u.js +0 -1
  542. package/static/dist/assets/flowDiagram-HODETNUW-kdpsp1je.js +0 -1
  543. package/static/dist/assets/host-i828xuo7.js +0 -1
  544. package/static/dist/assets/index-eh8zilnv.css +0 -2
  545. package/static/dist/assets/index-r648f4o1.js +0 -11
  546. package/static/dist/assets/lazyRouteComponent-e1y4t2i9.js +0 -1
  547. package/static/dist/assets/link-oot2yn73.js +0 -1
  548. package/static/dist/assets/login-gu28l9ou.js +0 -1
  549. package/static/dist/assets/mermaid-HWGCJPDP-mtwrj8fn.js +0 -1
  550. package/static/dist/assets/onboarding-lkphzu1s.js +0 -1
  551. package/static/dist/assets/onboarding_model_label-ftpjdwqk.js +0 -1
  552. package/static/dist/assets/railroad-abnf-HS5TGJTU-nf9d6pdx.js +0 -1
  553. package/static/dist/assets/railroad-ebnf-LZEXJU2U-f7ummkt5.js +0 -1
  554. package/static/dist/assets/railroad-peg-WCYAUIDC-oczq5tqs.js +0 -1
  555. package/static/dist/assets/refresh-abtu93ke.js +0 -1
  556. package/static/dist/assets/saved-girnabkf.js +0 -1
  557. package/static/dist/assets/settings_section_extensions_title-h14cwk6f.js +0 -1
  558. package/static/dist/assets/share._token-kl0r7ido.js +0 -2
  559. package/static/dist/assets/sliders-horizontal-n1zm34qq.js +0 -1
  560. package/static/dist/assets/sse-iwku0sjo.js +0 -1
  561. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-ixhx8r46.js +0 -1
  562. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-h9ersk2e.js +0 -8
  563. package/static/dist/assets/todoStore-jjr5yr78.js +0 -1
  564. package/static/dist/assets/units-kgxzxfhi.js +0 -1
  565. package/static/dist/assets/untitled-cikfjntn.js +0 -1
  566. package/static/dist/assets/useForm-kqoqg0k9.js +0 -1
  567. package/static/dist/assets/useLocale-o59tbiea.js +0 -1
@@ -1,21 +1,10 @@
1
+ import { markedStateDbGeneration } from './state-db-identity.js';
1
2
  import { str } from '../util.js';
2
- /**
3
- * The WebUI session store (Python `api/models.py`): `sessions/<sid>.json`
4
- * sidecars written atomically with a `.bak` on shrink, the sorted
5
- * `sessions/_index.json` sidebar index with incremental patching, a bounded
6
- * LRU of resident sessions that never evicts active or unsaved work, the
7
- * deleted-session tombstone, composer draft overlays, and per-session
8
- * mutation locks.
9
- *
10
- * The state.db projection (CLI sessions, lineage metadata, transcript
11
- * reconciliation) is not part of this store; it layers on in the sidebar
12
- * builder once the read-only SQLite projection lands.
13
- */
14
- import { closeSync, existsSync, fstatSync, fsyncSync, mkdirSync, openSync, readdirSync, readFileSync, renameSync, rmSync, statSync, unlinkSync } from 'node:fs';
15
- import { writeFully } from '../fs/atomic.js';
16
- import { basename, join } from 'node:path';
17
3
  import { sanitizeSessionRow } from './list.js';
4
+ import { withBackgroundDisplayKinds } from './background-updates.js';
18
5
  import { anchorSceneIndexFromRecords, collapseAdjacentDuplicatePartials, isSafeSessionId, Session } from './session.js';
6
+ import { RevisionConflict } from './storage/backend.js';
7
+ import { sessionSearchMatches, sessionSearchMessageText } from './storage/search-text.js';
19
8
  export class SessionNotFound extends Error {
20
9
  sid;
21
10
  constructor(sid) {
@@ -24,175 +13,44 @@ export class SessionNotFound extends Error {
24
13
  this.name = 'SessionNotFound';
25
14
  }
26
15
  }
27
- const LOAD_STABLE_READ_ATTEMPTS = 3;
28
16
  const UNSAVED_SHELL_GRACE_S = 1800;
29
- const STALE_TMP_AGE_S = 3600;
30
- const DELETED_TOMBSTONE_CAP = 1000;
31
- const DELETED_TOMBSTONE_VERSION = 1;
32
- let tmpCounter = 0;
33
- function tmpName(path) {
34
- return `${path}.tmp.${process.pid}.${++tmpCounter}`;
35
- }
36
- function writeAtomic(path, text) {
37
- const tmp = tmpName(path);
38
- const fd = openSync(tmp, 'w');
39
- try {
40
- writeFully(fd, text);
41
- fsyncSync(fd);
42
- closeSync(fd);
43
- renameSync(tmp, path);
44
- }
45
- catch (error) {
46
- try {
47
- closeSync(fd);
48
- }
49
- catch { /* closed */ }
50
- try {
51
- unlinkSync(tmp);
52
- }
53
- catch { /* gone */ }
54
- throw error;
55
- }
56
- }
57
- export function statSignature(path) {
58
- try {
59
- const st = statSync(path, { bigint: true });
60
- return `${path}:${st.mtimeNs}:${st.size}:${st.ctimeNs}`;
61
- }
62
- catch {
63
- return null;
64
- }
65
- }
66
- /** Python `_read_metadata_json_prefix`: everything before the top-level `messages` (or `anchor_activity_scenes`) key. */
67
- export function readMetadataJsonPrefix(path, maxBytes = 1024 * 1024) {
68
- return readMetadataJsonPrefixWithSignature(path, maxBytes).prefix;
69
- }
70
- /**
71
- * The metadata prefix together with the signature of the inode it was read from (fstat of the open descriptor), so a
72
- * file atomically replaced during the read is never cached under the replacement's identity.
73
- */
74
- export function readMetadataJsonPrefixWithSignature(path, maxBytes = 1024 * 1024) {
75
- const fd = openSync(path, 'r');
76
- try {
77
- const st = fstatSync(fd, { bigint: true });
78
- const signature = `${path}:${st.mtimeNs}:${st.size}:${st.ctimeNs}`;
79
- return { prefix: readPrefixFromFd(fd, maxBytes, Math.min(64 * 1024, maxBytes)), signature };
80
- }
81
- finally {
82
- closeSync(fd);
83
- }
84
- }
85
- function readPrefixFromFd(fd, maxBytes, initialStage) {
86
- let stage = initialStage;
87
- {
88
- let raw = Buffer.alloc(0);
89
- let text = '';
90
- let stopPos = null;
91
- while (raw.length < maxBytes) {
92
- const chunk = Buffer.alloc(Math.min(stage, maxBytes - raw.length));
93
- const n = readSyncFully(fd, chunk);
94
- if (n === 0)
95
- return null;
96
- raw = Buffer.concat([raw, chunk.subarray(0, n)]);
97
- text = raw.toString('utf8');
98
- const messagesPos = findTopLevelJsonKey(text, 'messages');
99
- const scenesPos = findTopLevelJsonKey(text, 'anchor_activity_scenes');
100
- stopPos = messagesPos;
101
- if (scenesPos !== null && (stopPos === null || scenesPos < stopPos))
102
- stopPos = scenesPos;
103
- if (stopPos !== null)
104
- break;
105
- stage *= 2;
106
- }
107
- if (stopPos === null)
108
- return null;
109
- let prefix = text.slice(0, stopPos).trimEnd();
110
- if (prefix.endsWith(','))
111
- prefix = prefix.slice(0, -1).trimEnd();
112
- return `${prefix}\n}`;
113
- }
114
- }
115
- import { readSync } from 'node:fs';
116
- function readSyncFully(fd, buf) {
117
- let total = 0;
118
- while (total < buf.length) {
119
- const n = readSync(fd, buf, total, buf.length - total, null);
120
- if (n === 0)
121
- break;
122
- total += n;
123
- }
124
- return total;
125
- }
126
- /** Offset of `"key":` at JSON nesting depth 1, or null. */
127
- export function findTopLevelJsonKey(text, key) {
128
- let depth = 0;
129
- let inString = false;
130
- let escape = false;
131
- let stringStart = -1;
132
- for (let i = 0; i < text.length; i += 1) {
133
- const ch = text[i];
134
- if (inString) {
135
- if (escape)
136
- escape = false;
137
- else if (ch === '\\')
138
- escape = true;
139
- else if (ch === '"') {
140
- inString = false;
141
- if (depth === 1 && text.slice(stringStart + 1, i) === key) {
142
- let j = i + 1;
143
- while (j < text.length && /\s/.test(text[j] ?? ''))
144
- j += 1;
145
- if (text[j] === ':')
146
- return stringStart;
147
- }
148
- }
149
- continue;
150
- }
151
- if (ch === '"') {
152
- inString = true;
153
- stringStart = i;
154
- continue;
155
- }
156
- if (ch === '{' || ch === '[')
157
- depth += 1;
158
- else if (ch === '}' || ch === ']')
159
- depth -= 1;
160
- }
161
- return null;
162
- }
163
- export function readPersistedMessageCount(path) {
164
- try {
165
- const prefix = readMetadataJsonPrefix(path);
166
- if (!prefix)
167
- return null;
168
- const value = JSON.parse(prefix).message_count;
169
- if (typeof value === 'boolean')
170
- return null;
171
- const n = Number(value);
172
- return Number.isInteger(n) && n >= 0 ? n : null;
173
- }
174
- catch {
175
- return null;
176
- }
177
- }
178
17
  export class SessionStore {
179
18
  deps;
180
- sessionDir;
181
- indexFile;
182
- /** Resident sessions in LRU order (oldest first). */
183
19
  sessions = new Map();
184
20
  locks = new Map();
185
- persistedIdsCache = null;
186
- parsedIndexCache = null;
187
- indexFlush = null;
21
+ writes = new Map();
22
+ resolutions = new Map();
23
+ persistedMessages = new WeakMap();
24
+ indexWriter = Promise.resolve();
188
25
  indexPending = new Map();
26
+ indexFlush = null;
189
27
  constructor(deps) {
190
28
  this.deps = deps;
191
- this.sessionDir = deps.sessionDir;
192
- this.indexFile = join(deps.sessionDir, '_index.json');
193
29
  }
194
- pathFor(sid) {
195
- return join(this.sessionDir, `${sid}.json`);
30
+ get backend() { return this.deps.storage.sessions; }
31
+ async exists(sid) { return this.backend.exists(sid); }
32
+ async revision(sid) { return this.backend.revision(sid); }
33
+ readMessages(sid, range) { return this.backend.readMessages(sid, range); }
34
+ async index() { return this.backend.index(); }
35
+ async hasIndex() { return this.backend.hasIndex(); }
36
+ get shrinkBackup() { return this.backend.shrinkBackup; }
37
+ async diagnose() { return this.backend.diagnose(); }
38
+ async repair(issue) {
39
+ const repaired = await this.backend.repair(issue);
40
+ if (repaired)
41
+ this.sessions.delete(issue.session_id);
42
+ return repaired;
43
+ }
44
+ async close() {
45
+ try {
46
+ await Promise.allSettled(this.resolutions.values());
47
+ await Promise.allSettled(this.writes.values());
48
+ await this.indexFlush;
49
+ await this.indexWriter;
50
+ }
51
+ finally {
52
+ await this.deps.storage.close();
53
+ }
196
54
  }
197
55
  // ── locks ────────────────────────────────────────────────────────────────
198
56
  /** Serialize one session's read-modify-write sequences (Python `_get_session_agent_lock`). */
@@ -222,17 +80,17 @@ export class SessionStore {
222
80
  }
223
81
  }
224
82
  // ── creation / resolution ────────────────────────────────────────────────
225
- construct(init, profile) {
226
- const session = new Session(init, this.deps.defaults(profile));
83
+ async construct(init, profile) {
84
+ const session = new Session(init, (await this.deps.defaults(profile)));
227
85
  // New-session defaults must not replace the title supplied by the read projection.
228
86
  if (typeof init.title === 'string')
229
87
  session.title = init.title;
230
88
  return session;
231
89
  }
232
90
  /** In-memory only until the first message is persisted (Python `new_session`). */
233
- newSession(opts = {}) {
91
+ async newSession(opts = {}) {
234
92
  const profile = opts.profile ?? null;
235
- const defaults = this.deps.defaults(profile);
93
+ const defaults = (await this.deps.defaults(profile));
236
94
  const wt = opts.worktree ?? null;
237
95
  const s = new Session({
238
96
  workspace: (wt ? wt.path : opts.workspace) || defaults.workspace,
@@ -247,97 +105,81 @@ export class SessionStore {
247
105
  worktree_created_at: wt?.created_at ?? null,
248
106
  enabled_toolsets: opts.enabledToolsets ?? null,
249
107
  }, defaults);
250
- this.clearDeletedTombstone(s.session_id);
251
- this.touch(s);
108
+ await this.clearDeletedTombstone(s.session_id);
109
+ await this.touch(s);
252
110
  if (wt)
253
- this.save(s);
111
+ (await this.save(s));
254
112
  return s;
255
113
  }
256
114
  /** Insert or refresh a session in the LRU and evict over the cap. */
257
- touch(session) {
115
+ async touch(session) {
258
116
  this.sessions.delete(session.session_id);
259
117
  this.sessions.set(session.session_id, session);
260
- this.evictOverCap();
118
+ await this.evictOverCap();
261
119
  }
262
- load(sid) {
120
+ async load(sid, opts = {}) {
263
121
  if (!isSafeSessionId(sid))
264
122
  return null;
265
- const path = this.pathFor(sid);
266
- if (!existsSync(path))
267
- return null;
268
- // Another process may replace the file atomically while we read it: retry until one read is bracketed by the
269
- // same signature. A read that never stabilises keeps a null signature, which `cachedLagsDisk` treats as stale.
270
- let data = {};
271
- let signature = null;
272
- for (let attempt = 0; attempt < LOAD_STABLE_READ_ATTEMPTS; attempt += 1) {
273
- const preSig = statSignature(path);
274
- data = JSON.parse(readFileSync(path, 'utf8'));
275
- const postSig = statSignature(path);
276
- if (preSig !== null && preSig === postSig) {
277
- signature = postSig;
278
- break;
123
+ for (let attempt = 0; attempt < 3; attempt += 1) {
124
+ const stored = await this.backend.readDocument(sid);
125
+ if (!stored)
126
+ return null;
127
+ const data = sanitizeSessionRow(stored.doc, this, sid);
128
+ if (data?.session_id !== sid)
129
+ return null;
130
+ const messages = await this.readMessages(sid, { start: 0, end: Infinity });
131
+ if (stored.revision === null || stored.revision !== await this.revision(sid))
132
+ continue;
133
+ const [rows, collapsed] = collapseAdjacentDuplicatePartials(messages);
134
+ data.messages = rows;
135
+ const session = (await this.construct(data, data.profile ?? null));
136
+ session.sidecarLoadedSignature = stored.revision;
137
+ this.persistedMessages.set(session, messages.map((row) => JSON.stringify(row)));
138
+ if (collapsed && (opts.repairDuplicates ?? true)) {
139
+ try {
140
+ await this.save(session, { touchUpdatedAt: false, skipIndex: true });
141
+ }
142
+ catch { /* best effort */ }
279
143
  }
280
- }
281
- const clean = sanitizeSessionRow(data, this, basename(path));
282
- if (!clean)
283
- return null;
284
- data = clean;
285
- const [messages, collapsed] = collapseAdjacentDuplicatePartials(data.messages);
286
- data.messages = messages;
287
- const session = this.construct(data, data.profile ?? null);
288
- if (collapsed) {
289
144
  try {
290
- this.save(session, { touchUpdatedAt: false, skipIndex: true });
145
+ if (opts.drafts ?? true)
146
+ session.composer_draft = (await this.deps.drafts.read(sid, session.composer_draft));
291
147
  }
292
- catch { /* best effort */ }
293
- }
294
- else {
295
- session.sidecarLoadedSignature = signature;
296
- }
297
- try {
298
- session.composer_draft = this.deps.drafts.read(sid, session.composer_draft);
148
+ catch { /* ignore */ }
149
+ return session;
299
150
  }
300
- catch { /* ignore */ }
301
- return session;
151
+ throw new RevisionConflict(sid);
302
152
  }
303
- loadMetadataOnly(sid, opts = {}) {
153
+ async loadMetadataOnly(sid, opts = {}) {
304
154
  if (!isSafeSessionId(sid))
305
155
  return null;
306
- const path = this.pathFor(sid);
307
- if (!existsSync(path))
308
- return null;
309
156
  try {
310
- const { prefix, signature } = readMetadataJsonPrefixWithSignature(path);
311
- if (!prefix)
312
- return this.load(sid);
313
- const parsed = sanitizeSessionRow(JSON.parse(prefix), this, basename(path));
314
- if (!parsed)
157
+ const stored = await this.backend.readDocument(sid, { metadataOnly: true });
158
+ if (!stored)
159
+ return null;
160
+ if (!stored.metadataOnly)
161
+ return (await this.load(sid));
162
+ const parsed = sanitizeSessionRow(stored.doc, this, sid);
163
+ if (parsed?.session_id !== sid)
315
164
  return null;
316
- for (const key of ['session_id', 'title', 'created_at', 'updated_at'])
317
- if (!(key in parsed))
318
- return this.load(sid);
319
- const sidecarCount = parsed.message_count;
320
- const modernCount = typeof sidecarCount === 'number' && Number.isInteger(sidecarCount) && sidecarCount >= 0 ? sidecarCount : null;
321
- if (modernCount === null && !('anchor_scene_index' in parsed))
322
- return this.load(sid);
323
165
  parsed.messages = [];
324
166
  parsed.tool_calls = [];
325
- const session = this.construct(parsed, parsed.profile ?? null);
326
- let indexCount = null;
327
- if (modernCount === null)
328
- indexCount = (opts.indexMessageCounts ?? this.indexMessageCounts()).get(sid) ?? null;
329
- const known = [indexCount, modernCount].filter((c) => c !== null);
330
- session.metadataMessageCount = known.length ? Math.max(...known) : null;
167
+ const session = (await this.construct(parsed, parsed.profile ?? null));
168
+ session.metadataMessageCount = stored.messageCount ?? (opts.indexMessageCounts ?? await this.indexMessageCounts()).get(sid) ?? null;
331
169
  session.loadedMetadataOnly = true;
332
- session.sidecarLoadedSignature = signature;
170
+ session.sidecarLoadedSignature = stored.revision;
333
171
  return session;
334
172
  }
335
173
  catch {
336
- return this.load(sid);
174
+ return (await this.load(sid));
337
175
  }
338
176
  }
339
- /** Resolve a session through the cache with disk-freshness checks (Python `get_session`). Throws `SessionNotFound`. */
340
177
  get(sid, opts = {}) {
178
+ return this.serializeSession(sid, () => this.getNow(sid, opts), this.resolutions);
179
+ }
180
+ async getNow(sid, opts) {
181
+ // A local commit updates the resident revision before a reader can mistake it for an external edit.
182
+ await this.writes.get(sid)?.catch(() => { });
341
183
  const cached = this.sessions.get(sid);
342
184
  if (cached) {
343
185
  if (cached.session_id !== sid) {
@@ -349,15 +191,13 @@ export class SessionStore {
349
191
  this.sessions.delete(sid);
350
192
  this.sessions.set(sid, cached);
351
193
  }
352
- const lags = this.cachedLagsDisk(cached);
353
- // A persisted session whose file vanished was deleted underneath us: evict it so nothing recreates the sidecar.
354
- if (lags && !existsSync(this.pathFor(sid))) {
194
+ const lags = await this.cachedLagsStorage(cached);
195
+ if (lags && !await this.exists(sid)) {
355
196
  this.sessions.delete(sid);
356
197
  throw new SessionNotFound(sid);
357
198
  }
358
- // A sidebar-only stub (metadata load) must be upgraded to the full transcript when messages are requested.
359
199
  if (!opts.metadataOnly && (cached.loadedMetadataOnly || lags)) {
360
- const fresh = this.load(sid);
200
+ const fresh = await this.load(sid);
361
201
  if (fresh) {
362
202
  this.sessions.set(sid, fresh);
363
203
  return fresh;
@@ -366,381 +206,263 @@ export class SessionStore {
366
206
  return cached;
367
207
  }
368
208
  }
369
- const loaded = opts.metadataOnly ? this.loadMetadataOnly(sid) : this.load(sid);
209
+ const loaded = opts.metadataOnly ? await this.loadMetadataOnly(sid) : await this.load(sid);
370
210
  if (!loaded)
371
211
  throw new SessionNotFound(sid);
372
212
  if (opts.cacheOnMiss ?? true)
373
- this.touch(loaded);
213
+ await this.touch(loaded);
374
214
  return loaded;
375
215
  }
376
- /** Reload a metadata-only stub before mutating persisted fields (Python `_ensure_full_session_before_mutation`). */
377
- ensureFull(sid, session) {
216
+ async ensureFull(sid, session) {
378
217
  if (!session.loadedMetadataOnly)
379
218
  return session;
380
- const full = this.load(sid);
219
+ const full = await this.load(sid);
381
220
  if (!full)
382
221
  throw new SessionNotFound(sid);
383
- this.touch(full);
222
+ await this.touch(full);
384
223
  return full;
385
224
  }
386
- /** A cached full session is stale when its sidecar changed on disk and it carries no unsaved runtime state. */
387
- cachedLagsDisk(cached) {
225
+ async cachedLagsStorage(cached) {
388
226
  if (cached.active_stream_id || cached.pending_user_message || cached.pending_started_at)
389
227
  return false;
390
- const current = statSignature(this.pathFor(cached.session_id));
391
- // A session that was persisted and whose file has since vanished was deleted underneath us: it is stale, and the
392
- // reload's miss evicts it rather than letting a later mutation recreate the deleted sidecar.
393
- if (current === null)
394
- return cached.sidecarLoadedSignature !== null;
395
- // An unknown read identity (the file changed underneath the load) must not be trusted: reload it.
396
- if (cached.sidecarLoadedSignature === null)
397
- return true;
398
- return current !== cached.sidecarLoadedSignature;
228
+ return await this.revision(cached.session_id) !== cached.sidecarLoadedSignature;
399
229
  }
400
- // ── persistence ──────────────────────────────────────────────────────────
401
230
  save(session, opts = {}) {
402
- if (!isSafeSessionId(session.session_id))
403
- throw new Error(`Unsafe session_id ${JSON.stringify(session.session_id)}; refusing to write outside session store`);
404
- if (session.loadedMetadataOnly) {
405
- throw new Error(`Refusing to save metadata-only session ${JSON.stringify(session.session_id)}: would atomically overwrite on-disk messages with []. Reload with metadata_only=False before mutating state. See #1558.`);
406
- }
407
- // TAL-493: a save carries a current state.db marker forward and never revives a stale one (an older release's save
408
- // moved `updated_at` past its stamp); only a fresh marker is made current.
231
+ return this.serializeSession(session.session_id, () => this.saveNow(session, opts));
232
+ }
233
+ serializeSession(sid, fn, queue = this.writes) {
234
+ const result = (queue.get(sid) ?? Promise.resolve()).then(fn);
235
+ const settled = result.then(() => { }, () => { });
236
+ queue.set(sid, settled);
237
+ return result.finally(() => { if (queue.get(sid) === settled)
238
+ queue.delete(sid); });
239
+ }
240
+ async saveNow(session, opts) {
241
+ const sid = session.session_id;
242
+ if (!isSafeSessionId(sid))
243
+ throw new Error(`Unsafe session_id ${JSON.stringify(sid)}; refusing to write outside session store`);
244
+ if (session.loadedMetadataOnly)
245
+ throw new Error(`Refusing to save metadata-only session ${JSON.stringify(sid)}: would overwrite messages with []. Reload before mutating state.`);
409
246
  const markerCurrent = session.state_db_seen_stamp === session.updated_at;
410
247
  if (opts.touchUpdatedAt ?? true)
411
248
  session.updated_at = this.deps.now();
412
249
  if (markerCurrent)
413
250
  session.state_db_seen_stamp = session.updated_at;
414
- const payload = JSON.stringify(session.toDocument(), null, 2);
415
- const path = session.path = this.pathFor(session.session_id);
416
- mkdirSync(this.sessionDir, { recursive: true });
417
- try {
418
- if (existsSync(path)) {
419
- const incoming = session.messages.length;
420
- let existingCount = readPersistedMessageCount(path);
421
- let existingText = null;
422
- if (existingCount === null) {
423
- existingText = readFileSync(path, 'utf8');
424
- try {
425
- const existing = JSON.parse(existingText);
426
- existingCount = Array.isArray(existing.messages) ? existing.messages.length : 0;
427
- }
428
- catch {
429
- existingCount = -1;
430
- }
431
- }
432
- if (existingCount > 0 && incoming === 0 && (session.active_stream_id || session.pending_user_message)) {
433
- this.deps.log(`[webui] WARNING: refusing to overwrite session ${session.session_id} messages with empty active/pending snapshot (existing=${existingCount}, incoming=${incoming}, stream=${session.active_stream_id ?? ''})`);
434
- return;
435
- }
436
- if (existingCount > incoming) {
437
- existingText ??= readFileSync(path, 'utf8');
438
- try {
439
- writeAtomic(`${path}.bak`, existingText);
440
- }
441
- catch { /* backup is best effort */ }
442
- }
251
+ session.messages = withBackgroundDisplayKinds(session.messages, session);
252
+ const prefix = session.prefixIndex;
253
+ let appendOnly = Boolean(prefix && session.messages.length >= prefix.length && prefix.generation === markedStateDbGeneration(session) && prefix.matchesToolCalls(session.tool_calls));
254
+ const payload = JSON.stringify(session.toDocument(), function (key, value) {
255
+ if (appendOnly && this === session.messages && Number(key) < prefix.length && !prefix.matchesRow(Number(key), value))
256
+ appendOnly = false;
257
+ return value;
258
+ });
259
+ const doc = JSON.parse(payload);
260
+ const rows = doc.messages;
261
+ delete doc.messages;
262
+ const snapshot = rows.map((row) => JSON.stringify(row));
263
+ const indexEntry = session.compact();
264
+ const previous = this.persistedMessages.get(session);
265
+ let messages = { kind: 'replace', rows };
266
+ if (previous) {
267
+ let from = 0;
268
+ while (from < previous.length && from < snapshot.length && previous[from] === snapshot[from])
269
+ from += 1;
270
+ if (from === previous.length)
271
+ messages = from === snapshot.length ? { kind: 'unchanged' } : { kind: 'append', rows: rows.slice(from) };
272
+ else if (snapshot.length >= previous.length) {
273
+ let tail = 0;
274
+ while (tail < previous.length - from && tail < snapshot.length - from && previous[previous.length - 1 - tail] === snapshot[snapshot.length - 1 - tail])
275
+ tail += 1;
276
+ messages = { kind: 'splice', from, deleteCount: previous.length - from - tail, rows: rows.slice(from, rows.length - tail) };
443
277
  }
444
278
  }
445
- catch { /* stat failures never block the save */ }
446
- writeAtomic(path, payload);
447
- session.sidecarLoadedSignature = statSignature(path);
448
- this.invalidatePersistedIds();
449
- if (!(opts.skipIndex ?? false))
450
- this.queueIndexUpdate([session]);
451
- if (session.messages.length)
452
- this.clearDeletedTombstone(session.session_id);
453
- }
454
- // ── index ────────────────────────────────────────────────────────────────
455
- cleanupStaleTmpFiles() {
456
- const cutoff = this.deps.now() - STALE_TMP_AGE_S;
457
- try {
458
- for (const name of readdirSync(this.sessionDir)) {
459
- if (!name.includes('.tmp.'))
460
- continue;
461
- const p = join(this.sessionDir, name);
462
- try {
463
- if (statSync(p).mtimeMs / 1000 < cutoff)
464
- unlinkSync(p);
465
- }
466
- catch { /* best effort */ }
279
+ const expected = session.sidecarLoadedSignature;
280
+ if (!rows.length && (session.active_stream_id || session.pending_user_message)) {
281
+ const stored = await this.backend.readDocument(sid, { metadataOnly: true });
282
+ if ((stored?.messageCount ?? 0) > 0) {
283
+ this.deps.log(`[webui] WARNING: refusing to overwrite session ${sid} messages with empty active/pending snapshot`);
284
+ return;
467
285
  }
468
286
  }
469
- catch { /* dir may not exist */ }
470
- }
471
- invalidatePersistedIds() {
472
- this.persistedIdsCache = null;
473
- }
474
- persistedIds() {
475
- let mtimeNs = null;
476
287
  try {
477
- mtimeNs = statSync(this.sessionDir, { bigint: true }).mtimeNs;
288
+ session.sidecarLoadedSignature = await this.backend.write(sid, { doc, messages }, expected);
478
289
  }
479
- catch {
480
- mtimeNs = null;
481
- }
482
- if (this.persistedIdsCache?.mtimeNs === mtimeNs && mtimeNs !== null)
483
- return this.persistedIdsCache.ids;
484
- const ids = new Set();
485
- try {
486
- for (const name of readdirSync(this.sessionDir))
487
- if (name.endsWith('.json') && !name.startsWith('_'))
488
- ids.add(name.slice(0, -5));
290
+ catch (error) {
291
+ if (error instanceof RevisionConflict) {
292
+ this.sessions.delete(sid);
293
+ const fresh = await this.load(sid, { repairDuplicates: false });
294
+ if (fresh)
295
+ this.sessions.set(sid, fresh);
296
+ }
297
+ throw error;
489
298
  }
490
- catch { /* empty */ }
491
- this.persistedIdsCache = { mtimeNs, ids };
492
- return ids;
493
- }
494
- hasPersistedSessionFiles() {
495
- return this.persistedIds().size > 0;
496
- }
497
- indexSignature() {
498
- return statSignature(this.indexFile);
499
- }
500
- /** Parsed `_index.json` rows via the stat-validated cache; throws on a corrupt index. */
501
- readIndexEntries() {
502
- const signature = this.indexSignature();
503
- if (this.parsedIndexCache && signature !== null && this.parsedIndexCache.signature === signature)
504
- return [...this.parsedIndexCache.entries];
505
- const entries = JSON.parse(readFileSync(this.indexFile, 'utf8'));
506
- if (!Array.isArray(entries))
507
- throw new Error('session index must be a list');
508
- if (signature !== null && signature === this.indexSignature())
509
- this.parsedIndexCache = { signature, entries: entries };
510
- return [...entries];
511
- }
512
- indexMessageCounts(entries) {
513
- const out = new Map();
299
+ this.persistedMessages.set(session, snapshot);
300
+ if (appendOnly)
301
+ prefix.savedAppend(rows, session.sidecarLoadedSignature);
302
+ else
303
+ delete session.prefixIndex;
304
+ if (!(opts.skipIndex ?? false))
305
+ await this.queueIndexUpdate([indexEntry]);
306
+ if (rows.length)
307
+ await this.clearDeletedTombstone(sid);
308
+ }
309
+ async searchMessages(sids, terms, depth) {
310
+ const matches = new Map();
311
+ const remaining = [];
312
+ for (const sid of sids) {
313
+ const cached = this.sessions.get(sid);
314
+ if (!cached || cached.loadedMetadataOnly || await this.cachedLagsStorage(cached)) {
315
+ remaining.push(sid);
316
+ continue;
317
+ }
318
+ const rows = depth ? cached.messages.slice(0, depth) : cached.messages;
319
+ const idx = rows.findIndex((row) => sessionSearchMatches(sessionSearchMessageText(row), terms));
320
+ if (idx >= 0)
321
+ matches.set(sid, { idx, text: sessionSearchMessageText(rows[idx]) });
322
+ }
323
+ for (const [sid, match] of await this.backend.searchMessages(remaining, terms, depth))
324
+ matches.set(sid, match);
325
+ return matches;
326
+ }
327
+ async persistedIds() { return this.backend.ids(); }
328
+ async hasPersistedSessionFiles() { return (await this.persistedIds()).size > 0; }
329
+ async readIndexEntries() { return (await this.backend.index()).entries; }
330
+ async indexMessageCounts(entries) {
514
331
  let rows = entries;
515
332
  if (!rows) {
516
333
  try {
517
- rows = existsSync(this.indexFile) ? this.readIndexEntries() : [];
334
+ rows = await this.readIndexEntries();
518
335
  }
519
336
  catch {
520
337
  rows = [];
521
338
  }
522
339
  }
340
+ const out = new Map();
523
341
  for (const row of rows) {
524
- const sid = str(row.session_id);
525
- const n = Number(row.message_count);
342
+ const sid = str(row.session_id), n = Number(row.message_count);
526
343
  if (sid && Number.isInteger(n) && n >= 0)
527
344
  out.set(sid, n);
528
345
  }
529
346
  return out;
530
347
  }
531
- writeIndexPayload(entries) {
532
- writeAtomic(this.indexFile, JSON.stringify(entries));
533
- const signature = this.indexSignature();
534
- if (signature !== null)
535
- this.parsedIndexCache = { signature, entries };
536
- }
537
- loadSessionFromPath(path) {
538
- try {
539
- const data = sanitizeSessionRow(JSON.parse(readFileSync(path, 'utf8')), this, basename(path));
540
- if (!data)
541
- return null;
542
- const [messages] = collapseAdjacentDuplicatePartials(data.messages);
543
- data.messages = messages;
544
- return this.construct(data, data.profile ?? null);
545
- }
546
- catch {
547
- return null;
548
- }
348
+ async serializeIndex(fn) {
349
+ const pending = this.indexWriter.then(fn, fn);
350
+ this.indexWriter = pending.catch(() => { });
351
+ return pending;
549
352
  }
550
- /**
551
- * Rebuild `_index.json` from scratch. Resident sessions without a sidecar join it unless `diskOnly` (TAL-259: a recovery
552
- * rebuild lists only persisted sessions, so an unsaved shell never reads as index drift).
553
- */
554
- rebuildIndex(opts = {}) {
555
- mkdirSync(this.sessionDir, { recursive: true });
556
- this.cleanupStaleTmpFiles();
557
- const entryMap = new Map();
558
- for (const name of readdirSync(this.sessionDir)) {
559
- if (!name.endsWith('.json') || name.startsWith('_'))
560
- continue;
561
- const s = this.loadSessionFromPath(join(this.sessionDir, name));
562
- if (!s)
563
- continue;
564
- const c = s.compact();
565
- const sid = str(c.session_id);
566
- if (!sid)
567
- continue;
568
- const existing = entryMap.get(sid);
569
- if (!existing || Number(c.message_count ?? 0) > Number(existing.message_count ?? 0))
570
- entryMap.set(sid, c);
353
+ async rebuildIndexNow(opts = {}) {
354
+ const map = new Map();
355
+ for (const sid of await this.persistedIds()) {
356
+ try {
357
+ const session = await this.load(sid, { drafts: false, repairDuplicates: false });
358
+ if (session)
359
+ map.set(session.session_id, session.compact());
360
+ }
361
+ catch { /* corrupt sessions are omitted */ }
571
362
  }
572
- const entries = [...entryMap.values()];
573
363
  if (!opts.diskOnly)
574
364
  for (const s of this.sessions.values())
575
- if (!entryMap.has(s.session_id))
576
- entries.push(s.compact());
577
- entries.sort((a, b) => Number(b.updated_at ?? 0) - Number(a.updated_at ?? 0));
578
- this.writeIndexPayload(entries);
579
- }
580
- /** Rebuild or patch `_index.json` (Python `_write_session_index`). */
581
- writeIndex(updates) {
582
- mkdirSync(this.sessionDir, { recursive: true });
583
- if (!updates || !existsSync(this.indexFile)) {
584
- this.rebuildIndex();
585
- return;
586
- }
587
- try {
588
- const onDisk = this.persistedIds();
589
- let existing = this.readIndexEntries();
590
- const inMemory = new Set(this.sessions.keys());
591
- const updatedMap = new Map();
592
- for (const update of updates) {
593
- if (update instanceof Session)
594
- updatedMap.set(update.session_id, update.compact());
595
- else if (typeof update.session_id === 'string' && update.session_id)
596
- updatedMap.set(update.session_id, update);
365
+ if (!map.has(s.session_id))
366
+ map.set(s.session_id, s.compact());
367
+ await this.backend.writeIndex([...map.values()].sort((a, b) => Number(b.updated_at ?? 0) - Number(a.updated_at ?? 0)));
368
+ }
369
+ async rebuildIndex(opts = {}) { await this.serializeIndex(async () => { await this.rebuildIndexNow(opts); }); }
370
+ async writeIndex(updates) {
371
+ const snapshots = updates?.map((s) => s instanceof Session ? s.compact() : s);
372
+ await this.serializeIndex(async () => {
373
+ if (!snapshots || !await this.backend.hasIndex()) {
374
+ await this.rebuildIndexNow();
375
+ return;
597
376
  }
598
- const filtered = existing.filter((e) => inMemory.has(String(e.session_id)) || onDisk.has(String(e.session_id)));
599
- let changed = filtered.length !== existing.length;
600
- existing = filtered;
601
- const positions = new Map(existing.map((e, i) => [String(e.session_id), i]));
602
- for (const [sid, entry] of updatedMap) {
603
- const position = positions.get(sid);
604
- if (position === undefined) {
605
- existing.push(entry);
606
- positions.set(sid, existing.length - 1);
607
- changed = true;
608
- }
609
- else if (JSON.stringify(existing[position]) !== JSON.stringify(entry)) {
610
- existing[position] = entry;
611
- changed = true;
612
- }
377
+ let rows;
378
+ try {
379
+ rows = await this.readIndexEntries();
613
380
  }
614
- const oldOrder = existing.map((e) => e.session_id).join('\u0000');
615
- existing.sort((a, b) => Number(b.updated_at ?? 0) - Number(a.updated_at ?? 0));
616
- if (oldOrder !== existing.map((e) => e.session_id).join('\u0000'))
617
- changed = true;
618
- if (!changed)
381
+ catch {
382
+ await this.rebuildIndexNow();
619
383
  return;
620
- this.writeIndexPayload(existing);
621
- }
622
- catch {
623
- this.writeIndex();
384
+ }
385
+ const previous = JSON.stringify(rows);
386
+ const ids = await this.persistedIds();
387
+ rows = rows.filter((r) => ids.has(str(r.session_id)) || this.sessions.has(str(r.session_id)));
388
+ const positions = new Map(rows.map((r, i) => [str(r.session_id), i]));
389
+ for (const row of snapshots) {
390
+ const sid = str(row.session_id);
391
+ if (!sid)
392
+ continue;
393
+ const i = positions.get(sid);
394
+ if (i === undefined) {
395
+ positions.set(sid, rows.length);
396
+ rows.push(row);
397
+ }
398
+ else
399
+ rows[i] = row;
400
+ }
401
+ rows.sort((a, b) => Number(b.updated_at ?? 0) - Number(a.updated_at ?? 0));
402
+ if (JSON.stringify(rows) !== previous)
403
+ await this.backend.writeIndex(rows);
404
+ });
405
+ }
406
+ queueIndexUpdate(updates) {
407
+ for (const update of updates) {
408
+ const row = update instanceof Session ? update.compact() : update;
409
+ const sid = str(row.session_id);
410
+ if (sid)
411
+ this.indexPending.set(sid, row);
624
412
  }
413
+ this.indexFlush ??= Promise.resolve().then(async () => {
414
+ try {
415
+ while (this.indexPending.size) {
416
+ const rows = [...this.indexPending.values()];
417
+ this.indexPending.clear();
418
+ await this.writeIndex(rows);
419
+ }
420
+ }
421
+ finally {
422
+ this.indexFlush = null;
423
+ }
424
+ });
425
+ return this.indexFlush;
625
426
  }
626
- /** Coalesce index rewrites from concurrent saves (Python `_queue_session_index_update`). */
627
- queueIndexUpdate(sessions) {
628
- for (const s of sessions)
629
- this.indexPending.set(s.session_id, s.compact());
630
- this.flushIndexNow();
631
- }
632
- flushIndexNow() {
633
- const pending = [...this.indexPending.values()];
634
- this.indexPending.clear();
635
- if (pending.length)
636
- this.writeIndex(pending);
637
- }
638
- pruneFromIndex(sid) {
639
- if (!sid || !existsSync(this.indexFile))
640
- return;
641
- try {
642
- const existing = JSON.parse(readFileSync(this.indexFile, 'utf8'));
643
- if (!Array.isArray(existing))
644
- throw new Error('session index must be a list');
645
- const pruned = existing.filter((e) => e.session_id !== sid);
646
- if (pruned.length === existing.length)
427
+ async pruneFromIndex(sid) {
428
+ await this.serializeIndex(async () => {
429
+ if (!sid || !await this.backend.hasIndex())
647
430
  return;
648
- this.writeIndexPayload(pruned);
649
- }
650
- catch {
651
- this.writeIndex();
652
- }
653
- }
654
- // ── tombstones ───────────────────────────────────────────────────────────
655
- get deletedTombstoneFile() {
656
- return join(this.sessionDir, '_deleted_webui_sessions.json');
657
- }
658
- loadDeletedTombstone() {
659
- try {
660
- if (!existsSync(this.deletedTombstoneFile))
661
- return new Set();
662
- const raw = JSON.parse(readFileSync(this.deletedTombstoneFile, 'utf8'));
663
- if (!raw || typeof raw !== 'object' || Number(raw.version) !== DELETED_TOMBSTONE_VERSION || !Array.isArray(raw.ids))
664
- return new Set();
665
- return new Set(raw.ids.map((v) => String(v ?? '').trim()).filter(Boolean));
666
- }
667
- catch {
668
- return new Set();
669
- }
670
- }
671
- saveDeletedTombstone(ids) {
672
- let sorted = [...ids].sort();
673
- if (sorted.length > DELETED_TOMBSTONE_CAP)
674
- sorted = sorted.slice(-DELETED_TOMBSTONE_CAP);
675
- try {
676
- mkdirSync(this.sessionDir, { recursive: true });
677
- writeAtomic(this.deletedTombstoneFile, JSON.stringify({ version: DELETED_TOMBSTONE_VERSION, ids: sorted }, null, 2));
678
- }
679
- catch { /* best effort */ }
680
- }
681
- recordDeletedTombstone(sid) {
682
- const ids = this.loadDeletedTombstone();
683
- if (ids.has(sid))
684
- return;
685
- ids.add(sid);
686
- this.saveDeletedTombstone(ids);
687
- }
688
- clearDeletedTombstone(sid) {
689
- const ids = this.loadDeletedTombstone();
690
- if (!ids.has(sid))
691
- return;
692
- ids.delete(sid);
693
- if (ids.size)
694
- this.saveDeletedTombstone(ids);
695
- else
696
- rmSync(this.deletedTombstoneFile, { force: true });
697
- }
698
- /** The index marked this id as a deleted WebUI session (Python `_marks_deleted_webui_session`, tombstone half). */
699
- wasDeleted(sid) {
700
- return this.loadDeletedTombstone().has(sid);
701
- }
702
- // ── eviction ─────────────────────────────────────────────────────────────
703
- persistedMessageCount(sid) {
704
- const path = this.pathFor(sid);
705
- if (!existsSync(path))
706
- return null;
707
- const count = readPersistedMessageCount(path);
708
- if (count !== null)
709
- return count;
710
- try {
711
- const data = JSON.parse(readFileSync(path, 'utf8'));
712
- return Array.isArray(data.messages) ? data.messages.length : 0;
713
- }
714
- catch {
715
- return null;
716
- }
431
+ try {
432
+ const rows = await this.readIndexEntries();
433
+ const pruned = rows.filter((r) => r.session_id !== sid);
434
+ if (pruned.length !== rows.length)
435
+ await this.backend.writeIndex(pruned);
436
+ }
437
+ catch {
438
+ await this.rebuildIndexNow();
439
+ }
440
+ });
717
441
  }
718
- /** Python `_session_is_evictable`: never drop active, pending, or unsaved sessions. */
719
- isEvictable(s) {
442
+ async loadDeletedTombstone() { return this.backend.tombstones.all(); }
443
+ async recordDeletedTombstone(sid) { return this.backend.tombstones.add(sid); }
444
+ async clearDeletedTombstone(sid) { return this.backend.tombstones.remove(sid); }
445
+ async wasDeleted(sid) { return this.backend.tombstones.has(sid); }
446
+ async isEvictable(s) {
720
447
  if (!s)
721
448
  return true;
722
- if (s.active_stream_id || s.pending_user_message || s.pending_started_at)
723
- return false;
724
- if (!s.session_id)
449
+ if (s.active_stream_id || s.pending_user_message || s.pending_started_at || !s.session_id)
725
450
  return false;
726
451
  if (s.loadedMetadataOnly)
727
452
  return true;
728
- const inMemory = s.messages.length;
729
- const diskCount = this.persistedMessageCount(s.session_id);
730
- if (diskCount === null) {
731
- if (inMemory > 0)
732
- return false;
733
- if (Object.keys(s.composer_draft).length)
734
- return false;
735
- if (existsSync(this.pathFor(s.session_id)))
453
+ let count = null;
454
+ try {
455
+ count = (await this.backend.readDocument(s.session_id, { metadataOnly: true }))?.messageCount ?? null;
456
+ }
457
+ catch { /* corrupt */ }
458
+ if (count === null) {
459
+ if (s.messages.length || Object.keys(s.composer_draft).length || await this.exists(s.session_id))
736
460
  return false;
737
461
  return this.deps.now() - s.created_at > UNSAVED_SHELL_GRACE_S;
738
462
  }
739
- if (inMemory === 0)
740
- return true;
741
- return diskCount >= inMemory;
463
+ return count >= s.messages.length;
742
464
  }
743
- evictOverCap(cap) {
465
+ async evictOverCap(cap) {
744
466
  let limit = cap ?? this.deps.cacheMax();
745
467
  if (!Number.isInteger(limit) || limit < 1)
746
468
  limit = 100;
@@ -748,63 +470,35 @@ export class SessionStore {
748
470
  for (const sid of [...this.sessions.keys()]) {
749
471
  if (this.sessions.size <= limit)
750
472
  break;
751
- if (this.isEvictable(this.sessions.get(sid))) {
473
+ const session = this.sessions.get(sid);
474
+ if (await this.isEvictable(session) && this.sessions.get(sid) === session && !session?.active_stream_id && !session?.pending_user_message && !session?.pending_started_at) {
752
475
  this.sessions.delete(sid);
753
476
  evicted += 1;
754
477
  }
755
478
  }
756
479
  return evicted;
757
480
  }
758
- // ── deletion ─────────────────────────────────────────────────────────────
759
- /** Remove the sidecar, backup, draft, index row, and tombstone the id. Returns false when the sidecar survived. */
760
- deleteFiles(sid, opts = {}) {
761
- const path = this.pathFor(sid);
762
- try {
763
- rmSync(path, { force: true });
764
- }
765
- catch {
766
- return false;
767
- }
768
- if (existsSync(path))
769
- return false;
770
- this.sessions.delete(sid);
771
- this.invalidatePersistedIds();
772
- try {
773
- this.pruneFromIndex(sid);
774
- }
775
- catch { /* ignore */ }
776
- try {
777
- rmSync(`${path}.bak`, { force: true });
778
- }
779
- catch { /* ignore */ }
780
- try {
781
- this.deps.drafts.delete(sid);
782
- }
783
- catch { /* ignore */ }
784
- // TAL-372: the session's background work records (`BackgroundTaskStore`) go with it.
785
- try {
786
- rmSync(join(this.sessionDir, '_background', `${sid}.json`), { force: true });
787
- }
788
- catch { /* ignore */ }
789
- this.deps.onDeleted?.(sid);
790
- if (opts.tombstone ?? true)
791
- this.recordDeletedTombstone(sid);
792
- return true;
481
+ delete(sid, opts = {}) {
482
+ return this.serializeSession(sid, async () => {
483
+ if (!await this.backend.delete(sid, { tombstone: opts.tombstone ?? true }))
484
+ return false;
485
+ this.sessions.delete(sid);
486
+ try {
487
+ await this.pruneFromIndex(sid);
488
+ }
489
+ catch { /* ignore */ }
490
+ try {
491
+ (await this.deps.drafts.delete(sid));
492
+ }
493
+ catch { /* ignore */ }
494
+ await this.deps.onDeleted?.(sid);
495
+ return true;
496
+ });
793
497
  }
794
- /** Every persisted session as a fresh full load (Python full-scan paths). */
795
- scanAll() {
498
+ async scanAll() {
796
499
  const out = [];
797
- let names = [];
798
- try {
799
- names = readdirSync(this.sessionDir);
800
- }
801
- catch {
802
- return out;
803
- }
804
- for (const name of names) {
805
- if (!name.endsWith('.json') || name.startsWith('_'))
806
- continue;
807
- const s = this.load(name.slice(0, -5));
500
+ for (const sid of await this.persistedIds()) {
501
+ const s = await this.load(sid);
808
502
  if (s)
809
503
  out.push(s);
810
504
  }