spiracha 1.3.3 → 1.5.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 (242) hide show
  1. package/AGENTS.md +55 -6
  2. package/README.md +46 -5
  3. package/apps/ui/AGENTS.md +31 -4
  4. package/apps/ui/README.md +59 -5
  5. package/apps/ui/dist/client/assets/analytics-DOy1b6Sh.js +1 -0
  6. package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-DqySVO2K.js +7 -0
  7. package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-KAcXtNxI.js +1 -0
  8. package/apps/ui/dist/client/assets/antigravity-keychain-panel-B77qJz6f.js +1 -0
  9. package/apps/ui/dist/client/assets/antigravity._workspaceKey-B3NM6gRH.js +1 -0
  10. package/apps/ui/dist/client/assets/antigravity._workspaceKey-ByitGWki.js +1 -0
  11. package/apps/ui/dist/client/assets/antigravity.index-DP9JC2P9.js +1 -0
  12. package/apps/ui/dist/client/assets/antigravity.index-DgW25FEV.js +1 -0
  13. package/apps/ui/dist/client/assets/badge-C3i4OKs8.js +1 -0
  14. package/apps/ui/dist/client/assets/checkbox-CdHitXWQ.js +1 -0
  15. package/apps/ui/dist/client/assets/claude-code-sessions._sessionId-8pQ55ci2.js +1 -0
  16. package/apps/ui/dist/client/assets/claude-code-sessions._sessionId-DVz5UmtU.js +1 -0
  17. package/apps/ui/dist/client/assets/claude-code._workspaceKey-BBKskSum.js +1 -0
  18. package/apps/ui/dist/client/assets/claude-code._workspaceKey-OkikMUFs.js +1 -0
  19. package/apps/ui/dist/client/assets/claude-code.index-Cwu76dbb.js +1 -0
  20. package/apps/ui/dist/client/assets/claude-code.index-D6eMd08t.js +1 -0
  21. package/apps/ui/dist/client/assets/createServerFn-B-KkwmHU.js +3 -0
  22. package/apps/ui/dist/client/assets/cursor-threads._composerId-CmBFclK8.js +1 -0
  23. package/apps/ui/dist/client/assets/cursor-threads._composerId-hBWlP1D-.js +1 -0
  24. package/apps/ui/dist/client/assets/cursor._workspaceKey-BFPkIRi-.js +1 -0
  25. package/apps/ui/dist/client/assets/cursor._workspaceKey-CaAZjGmm.js +1 -0
  26. package/apps/ui/dist/client/assets/cursor.index-8y4o20kb.js +2 -0
  27. package/apps/ui/dist/client/assets/cursor.index-CrnMwDCi.js +1 -0
  28. package/apps/ui/dist/client/assets/{data-table-DeQq8xdt.js → data-table-Br78_dk3.js} +2 -2
  29. package/apps/ui/dist/client/assets/delete-confirm-dialog-Bxs07u-j.js +7 -0
  30. package/apps/ui/dist/client/assets/dist-B9pyYCVU.js +1 -0
  31. package/apps/ui/dist/client/assets/dist-BrJhN2A0.js +5 -0
  32. package/apps/ui/dist/client/assets/dist-Cmd3AIfD.js +1 -0
  33. package/apps/ui/dist/client/assets/dist-DvMS2965.js +1 -0
  34. package/apps/ui/dist/client/assets/download-Bf0QJKD3.js +1 -0
  35. package/apps/ui/dist/client/assets/dropdown-menu-D5cvPaZt.js +1 -0
  36. package/apps/ui/dist/client/assets/es2015-ad6040dw.js +41 -0
  37. package/apps/ui/dist/client/assets/export-dialog-Dr8d2MqD.js +1 -0
  38. package/apps/ui/dist/client/assets/formatters-BtqyrX_c.js +1 -0
  39. package/apps/ui/dist/client/assets/index-C-STgIH4.js +166 -0
  40. package/apps/ui/dist/client/assets/jsx-runtime-DGeXAQPT.js +1 -0
  41. package/apps/ui/dist/client/assets/kiro-sessions._sessionId-CKgfJ7GR.js +1 -0
  42. package/apps/ui/dist/client/assets/kiro-sessions._sessionId-zvIJHtQF.js +1 -0
  43. package/apps/ui/dist/client/assets/kiro._workspaceKey-DhVuJuZB.js +1 -0
  44. package/apps/ui/dist/client/assets/kiro._workspaceKey-QArsjkTY.js +1 -0
  45. package/apps/ui/dist/client/assets/kiro.index-C-fr4m7i.js +1 -0
  46. package/apps/ui/dist/client/assets/kiro.index-OYgAdtZd.js +1 -0
  47. package/apps/ui/dist/client/assets/metric-card-Dplm0ZiM.js +1 -0
  48. package/apps/ui/dist/client/assets/opencode-sessions._sessionId-DLjiTst-.js +1 -0
  49. package/apps/ui/dist/client/assets/opencode-sessions._sessionId-biBwrRbt.js +8 -0
  50. package/apps/ui/dist/client/assets/opencode._workspaceKey-C7OIzMOJ.js +1 -0
  51. package/apps/ui/dist/client/assets/opencode._workspaceKey-sBakX0oI.js +1 -0
  52. package/apps/ui/dist/client/assets/opencode.index-Bl_kNs9N.js +1 -0
  53. package/apps/ui/dist/client/assets/opencode.index-CN53bVmb.js +1 -0
  54. package/apps/ui/dist/client/assets/page-header-Cwa3p6Tl.js +1 -0
  55. package/apps/ui/dist/client/assets/preload-helper-B8fFrdxC.js +1 -0
  56. package/apps/ui/dist/client/assets/projects._project-D8WloxQN.js +1 -0
  57. package/apps/ui/dist/client/assets/projects._project-DSDwH4NF.js +1 -0
  58. package/apps/ui/dist/client/assets/projects.index-5xRtuJdX.js +3 -0
  59. package/apps/ui/dist/client/assets/projects.index-DEfhWqa8.js +1 -0
  60. package/apps/ui/dist/client/assets/react-dom-DL96Jor4.js +1 -0
  61. package/apps/ui/dist/client/assets/refresh-ccw-BgKjOiA6.js +1 -0
  62. package/apps/ui/dist/client/assets/reload-error-panel-AHqu0akK.js +1 -0
  63. package/apps/ui/dist/client/assets/routes-DnzF_9CX.js +1 -0
  64. package/apps/ui/dist/client/assets/scroll-text-fM0sMOB3.js +1 -0
  65. package/apps/ui/dist/client/assets/select-CYF6pF37.js +1 -0
  66. package/apps/ui/dist/client/assets/settings-Dx70jgP_.js +1 -0
  67. package/apps/ui/dist/client/assets/sqlite-error-B6uZjUe8.js +1 -0
  68. package/apps/ui/dist/client/assets/styles-B7Zi9EFA.css +1 -0
  69. package/apps/ui/dist/client/assets/tabs-CmuhkT-c.js +7 -0
  70. package/apps/ui/dist/client/assets/text-filter-DVAZySUS.js +2 -0
  71. package/apps/ui/dist/client/assets/thread-transcript-stats-BUNz6u7F.js +1 -0
  72. package/apps/ui/dist/client/assets/threads._threadId-BB9tTsV4.js +1 -0
  73. package/apps/ui/dist/client/assets/threads._threadId-BMRSFow3.js +1 -0
  74. package/apps/ui/dist/client/assets/useMutation-BSFult2i.js +1 -0
  75. package/apps/ui/dist/client/assets/useQuery-CjINKZf1.js +1 -0
  76. package/apps/ui/dist/client/icon.svg +2 -1
  77. package/apps/ui/dist/server/assets/_tanstack-start-manifest_v-CQ8Jjs3B.js +387 -0
  78. package/apps/ui/dist/server/assets/analytics-CYP_LT5Y.js +16 -0
  79. package/apps/ui/dist/server/assets/{analytics-Dc_QDZZC.js → analytics-CtpOMVMO.js} +13 -6
  80. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-BKOyL2T6.js +613 -0
  81. package/apps/ui/dist/server/assets/{antigravity-conversations._conversationId-C9z4AeN-.js → antigravity-conversations._conversationId-eS87Dx7g.js} +2 -2
  82. package/apps/ui/dist/server/assets/{antigravity-db-D9gW1D8G.js → antigravity-db-Bh8_U9uw.js} +11 -7
  83. package/apps/ui/dist/server/assets/{antigravity-keychain-panel-bpPoxhyp.js → antigravity-keychain-panel-CC7FLmAK.js} +1 -1
  84. package/apps/ui/dist/server/assets/{antigravity-queries-DVIKyOXi.js → antigravity-queries-CGrJO9Vr.js} +5 -5
  85. package/apps/ui/dist/server/assets/{antigravity-server-Cp1lc8fp.js → antigravity-server-CHRjVFqN.js} +11 -11
  86. package/apps/ui/dist/server/assets/{antigravity._workspaceKey-Dl8o1ptg.js → antigravity._workspaceKey-B0QeYUOh.js} +2 -2
  87. package/apps/ui/dist/server/assets/{antigravity._workspaceKey-BAoa4izU.js → antigravity._workspaceKey-C3kP-qyN.js} +4 -4
  88. package/apps/ui/dist/server/assets/{antigravity.index-BOlrJasZ.js → antigravity.index-CPCYkqCi.js} +2 -2
  89. package/apps/ui/dist/server/assets/claude-code-db-Ik3VStKZ.js +361 -0
  90. package/apps/ui/dist/server/assets/claude-code-queries-Cgzi4K-e.js +37 -0
  91. package/apps/ui/dist/server/assets/claude-code-server-PfraClXS.js +72 -0
  92. package/apps/ui/dist/server/assets/claude-code-sessions._sessionId-C55oe6zj.js +18 -0
  93. package/apps/ui/dist/server/assets/claude-code-sessions._sessionId-D1RYYi3j.js +473 -0
  94. package/apps/ui/dist/server/assets/claude-code-sessions._sessionId-DSvPLEUL.js +11 -0
  95. package/apps/ui/dist/server/assets/claude-code-transcript-C-bxh3cS.js +139 -0
  96. package/apps/ui/dist/server/assets/claude-code-transcript-phase-DmUMc8VU.js +10 -0
  97. package/apps/ui/dist/server/assets/claude-code._workspaceKey-B5Xg_-tE.js +28 -0
  98. package/apps/ui/dist/server/assets/claude-code._workspaceKey-BPlzyXPa.js +11 -0
  99. package/apps/ui/dist/server/assets/claude-code._workspaceKey-DqDve2wl.js +191 -0
  100. package/apps/ui/dist/server/assets/claude-code.index-B7yYR7dD.js +92 -0
  101. package/apps/ui/dist/server/assets/claude-code.index-BUHThG1W.js +11 -0
  102. package/apps/ui/dist/server/assets/{codex-queries-ev_mRUzr.js → codex-queries-CtgeZ7VL.js} +15 -13
  103. package/apps/ui/dist/server/assets/{codex-server-DIYFLozB.js → codex-server-CGXU4H8-.js} +421 -146
  104. package/apps/ui/dist/server/assets/concurrency-VXtYvlGh.js +18 -0
  105. package/apps/ui/dist/server/assets/{createServerRpc-Cg-Zr1rq.js → createServerRpc-CF_DEwnm.js} +1 -1
  106. package/apps/ui/dist/server/assets/{createSsrRpc-DVnVQhGx.js → createSsrRpc-C754NPki.js} +1 -1
  107. package/apps/ui/dist/server/assets/{cursor-db-B7agkAvM.js → cursor-db-CZnYy7aT.js} +196 -9
  108. package/apps/ui/dist/server/assets/{cursor-queries-f7DjBKSD.js → cursor-queries-NCIM0Nat.js} +12 -10
  109. package/apps/ui/dist/server/assets/{cursor-recovery-9bJLs7vG.js → cursor-recovery-CXpO8KcD.js} +2 -2
  110. package/apps/ui/dist/server/assets/{cursor-server-nPzojHN3.js → cursor-server-DNu52A3K.js} +71 -42
  111. package/apps/ui/dist/server/assets/cursor-threads._composerId-Ckn6ItHI.js +582 -0
  112. package/apps/ui/dist/server/assets/{cursor-threads._composerId-B3qcsh9C.js → cursor-threads._composerId-p9-ARAzc.js} +2 -2
  113. package/apps/ui/dist/server/assets/{cursor-transcript-2iL3KFSK.js → cursor-transcript-B7tGnqtn.js} +1 -1
  114. package/apps/ui/dist/server/assets/{cursor._workspaceKey-BL_CQyHQ.js → cursor._workspaceKey-CZuohIUL.js} +2 -2
  115. package/apps/ui/dist/server/assets/{cursor._workspaceKey-BhQeepXB.js → cursor._workspaceKey-DPp5JSjy.js} +11 -9
  116. package/apps/ui/dist/server/assets/{cursor.index-CsrdckZD.js → cursor.index-DkWT-mxw.js} +2 -2
  117. package/apps/ui/dist/server/assets/{delete-confirm-dialog-PCD7S0_M.js → delete-confirm-dialog-BcBNCxWB.js} +5 -1
  118. package/apps/ui/dist/server/assets/{empty-plugin-adapters-D3qLMPfC.js → empty-plugin-adapters-B3lHh1La.js} +1 -1
  119. package/apps/ui/dist/server/assets/{export-dialog-DaPlOGFT.js → export-dialog-D88ze9Gy.js} +54 -12
  120. package/apps/ui/dist/server/assets/kiro-db--fXrED_-.js +552 -0
  121. package/apps/ui/dist/server/assets/kiro-queries-CzNlM2ok.js +37 -0
  122. package/apps/ui/dist/server/assets/kiro-server-9WRxg2Au.js +72 -0
  123. package/apps/ui/dist/server/assets/kiro-sessions._sessionId-CRY2c7YO.js +11 -0
  124. package/apps/ui/dist/server/assets/kiro-sessions._sessionId-F1UlwxmR.js +449 -0
  125. package/apps/ui/dist/server/assets/kiro-sessions._sessionId-bvTulT7C.js +18 -0
  126. package/apps/ui/dist/server/assets/kiro-transcript-DoekQBaH.js +112 -0
  127. package/apps/ui/dist/server/assets/kiro-transcript-phase-CJG40nQg.js +27 -0
  128. package/apps/ui/dist/server/assets/kiro._workspaceKey-BDe9sQwo.js +11 -0
  129. package/apps/ui/dist/server/assets/kiro._workspaceKey-CJsjgNwO.js +28 -0
  130. package/apps/ui/dist/server/assets/kiro._workspaceKey-D_-4Qb7y.js +193 -0
  131. package/apps/ui/dist/server/assets/kiro.index-C__-UROk.js +11 -0
  132. package/apps/ui/dist/server/assets/kiro.index-CmTbqeYj.js +93 -0
  133. package/apps/ui/dist/server/assets/opencode-db-BM7KjOzc.js +397 -0
  134. package/apps/ui/dist/server/assets/opencode-queries-BeNYXnQv.js +50 -0
  135. package/apps/ui/dist/server/assets/opencode-server-BpQ_kRze.js +72 -0
  136. package/apps/ui/dist/server/assets/opencode-sessions._sessionId-CGQ-t0vD.js +518 -0
  137. package/apps/ui/dist/server/assets/opencode-sessions._sessionId-D8Q7in_Z.js +18 -0
  138. package/apps/ui/dist/server/assets/opencode-sessions._sessionId-DKOvi0lV.js +11 -0
  139. package/apps/ui/dist/server/assets/opencode-think-tags-CAoD-EcZ.js +87 -0
  140. package/apps/ui/dist/server/assets/opencode-transcript-BNTcGNym.js +141 -0
  141. package/apps/ui/dist/server/assets/opencode-transcript-phase-OqLkiQD0.js +32 -0
  142. package/apps/ui/dist/server/assets/opencode._workspaceKey-BdqcWHw1.js +11 -0
  143. package/apps/ui/dist/server/assets/opencode._workspaceKey-CfH96HTT.js +28 -0
  144. package/apps/ui/dist/server/assets/opencode._workspaceKey-DdbHPDrO.js +207 -0
  145. package/apps/ui/dist/server/assets/opencode.index-BbVzkJXP.js +11 -0
  146. package/apps/ui/dist/server/assets/opencode.index-Vh_Gq_1L.js +93 -0
  147. package/apps/ui/dist/server/assets/{projects._project-CqqUFr4l.js → projects._project-4io5LO0E.js} +5 -3
  148. package/apps/ui/dist/server/assets/{projects._project-DahGqNYS.js → projects._project-D6ZoGhJ8.js} +42 -12
  149. package/apps/ui/dist/server/assets/{projects.index-CkTeFWNU.js → projects.index-BkLiF2FF.js} +21 -7
  150. package/apps/ui/dist/server/assets/projects.index-DesYXwfi.js +14 -0
  151. package/apps/ui/dist/server/assets/route-search-ts4W9MJ9.js +38 -0
  152. package/apps/ui/dist/server/assets/router-Sac2DGk6.js +518 -0
  153. package/apps/ui/dist/server/assets/{routes-Xix3ZeSo.js → routes-Bsact1uB.js} +2 -2
  154. package/apps/ui/dist/server/assets/{routes-0x1U0IHq.js → routes-BzkJgq4W.js} +2 -2
  155. package/apps/ui/dist/server/assets/{shared-CPRNYIql.js → shared-DyhChtHf.js} +4 -1
  156. package/apps/ui/dist/server/assets/source-session-export-server-DNutCawb.js +38 -0
  157. package/apps/ui/dist/server/assets/start-B_HFwkkk.js +4 -0
  158. package/apps/ui/dist/server/assets/tabs-DkaChNVg.js +501 -0
  159. package/apps/ui/dist/server/assets/thread-transcript-stats-Dh34mt2u.js +45 -0
  160. package/apps/ui/dist/server/assets/threads._threadId-BeXEc4Lo.js +648 -0
  161. package/apps/ui/dist/server/assets/{threads._threadId-BLCkGW--.js → threads._threadId-Ci0S4mKU.js} +2 -2
  162. package/apps/ui/dist/server/assets/ui-export-files-BHLX9bhN.js +83 -0
  163. package/apps/ui/dist/server/server.js +141 -83
  164. package/package.json +28 -11
  165. package/src/lib/antigravity-db.ts +20 -13
  166. package/src/lib/claude-code-db.ts +655 -0
  167. package/src/lib/claude-code-exporter-types.ts +110 -0
  168. package/src/lib/claude-code-transcript-phase.ts +25 -0
  169. package/src/lib/claude-code-transcript.ts +180 -0
  170. package/src/lib/codex-analytics.ts +118 -18
  171. package/src/lib/codex-browser-db.ts +491 -56
  172. package/src/lib/codex-browser-export.ts +27 -71
  173. package/src/lib/codex-thread-cache.ts +17 -1
  174. package/src/lib/concurrency.ts +25 -0
  175. package/src/lib/cursor-db.ts +318 -7
  176. package/src/lib/cursor-exporter.ts +2 -2
  177. package/src/lib/kiro-db.ts +949 -0
  178. package/src/lib/kiro-exporter-types.ts +100 -0
  179. package/src/lib/kiro-transcript-phase.ts +41 -0
  180. package/src/lib/kiro-transcript.ts +115 -0
  181. package/src/lib/opencode-db.ts +610 -0
  182. package/src/lib/opencode-exporter-types.ts +129 -0
  183. package/src/lib/opencode-think-tags.ts +126 -0
  184. package/src/lib/opencode-transcript-phase.ts +50 -0
  185. package/src/lib/opencode-transcript.ts +194 -0
  186. package/src/lib/shared.ts +25 -0
  187. package/src/lib/ui-cache.ts +14 -1
  188. package/src/lib/ui-export-archive.ts +61 -0
  189. package/apps/ui/dist/client/assets/analytics-C83LcWNb.js +0 -1
  190. package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-DkY8EEpK.js +0 -1
  191. package/apps/ui/dist/client/assets/antigravity-conversations._conversationId-OraHuz8j.js +0 -1
  192. package/apps/ui/dist/client/assets/antigravity-keychain-panel-DnDcq1ge.js +0 -1
  193. package/apps/ui/dist/client/assets/antigravity._workspaceKey-CQaZscA5.js +0 -1
  194. package/apps/ui/dist/client/assets/antigravity._workspaceKey-DTs99obU.js +0 -1
  195. package/apps/ui/dist/client/assets/antigravity.index-DbJa715k.js +0 -1
  196. package/apps/ui/dist/client/assets/antigravity.index-P-rs6w5X.js +0 -1
  197. package/apps/ui/dist/client/assets/badge-wgTVp-aq.js +0 -1
  198. package/apps/ui/dist/client/assets/checkbox-BUx0Gkuf.js +0 -1
  199. package/apps/ui/dist/client/assets/cursor-threads._composerId-D0DFXt8t.js +0 -1
  200. package/apps/ui/dist/client/assets/cursor-threads._composerId-tFa6Cf6A.js +0 -1
  201. package/apps/ui/dist/client/assets/cursor._workspaceKey-Df4PfA2k.js +0 -1
  202. package/apps/ui/dist/client/assets/cursor._workspaceKey-DwrzEtnV.js +0 -1
  203. package/apps/ui/dist/client/assets/cursor.index-CNBf4ZiR.js +0 -1
  204. package/apps/ui/dist/client/assets/cursor.index-DP9Hy8X6.js +0 -2
  205. package/apps/ui/dist/client/assets/delete-confirm-dialog-CPlNsLhM.js +0 -11
  206. package/apps/ui/dist/client/assets/dist-Ds3kS91l.js +0 -1
  207. package/apps/ui/dist/client/assets/download-DLuPnxpV.js +0 -1
  208. package/apps/ui/dist/client/assets/dropdown-menu-lFk6zjoL.js +0 -1
  209. package/apps/ui/dist/client/assets/es2015-D-H_iZF6.js +0 -41
  210. package/apps/ui/dist/client/assets/export-dialog-CyqyvwiP.js +0 -1
  211. package/apps/ui/dist/client/assets/formatters-DbCKP02D.js +0 -1
  212. package/apps/ui/dist/client/assets/index-C-ZPpKJ0.js +0 -22
  213. package/apps/ui/dist/client/assets/json-panel-BsngvucB.js +0 -1
  214. package/apps/ui/dist/client/assets/metadata-section-Dtl8dNag.js +0 -1
  215. package/apps/ui/dist/client/assets/metric-card-DNzR1Rne.js +0 -1
  216. package/apps/ui/dist/client/assets/page-header-dha_O7ZL.js +0 -1
  217. package/apps/ui/dist/client/assets/projects._project-B7HBhbXv.js +0 -1
  218. package/apps/ui/dist/client/assets/projects._project-DHVvuT7A.js +0 -1
  219. package/apps/ui/dist/client/assets/projects.index-BUUtx2AZ.js +0 -1
  220. package/apps/ui/dist/client/assets/projects.index-BojAe1Y4.js +0 -3
  221. package/apps/ui/dist/client/assets/refresh-ccw-B-ASKHtx.js +0 -1
  222. package/apps/ui/dist/client/assets/reload-error-panel-DD8M1BIY.js +0 -1
  223. package/apps/ui/dist/client/assets/routes-q7bzFp35.js +0 -1
  224. package/apps/ui/dist/client/assets/scroll-text-TvHyiJ_N.js +0 -1
  225. package/apps/ui/dist/client/assets/select-BFv1HMiT.js +0 -1
  226. package/apps/ui/dist/client/assets/settings-CvegTo4F.js +0 -1
  227. package/apps/ui/dist/client/assets/styles-Ch0r3kMZ.css +0 -1
  228. package/apps/ui/dist/client/assets/text-document-panel-D7Rws41o.js +0 -1
  229. package/apps/ui/dist/client/assets/text-filter-2q5pEoZh.js +0 -2
  230. package/apps/ui/dist/client/assets/threads._threadId-LpZVUlPq.js +0 -7
  231. package/apps/ui/dist/client/assets/threads._threadId-MSmNIBkq.js +0 -1
  232. package/apps/ui/dist/client/assets/useMutation-BOPJU9e0.js +0 -1
  233. package/apps/ui/dist/client/assets/useQuery-CbxZiVGF.js +0 -1
  234. package/apps/ui/dist/server/assets/_tanstack-start-manifest_v-zvGzK-Yx.js +0 -229
  235. package/apps/ui/dist/server/assets/antigravity-conversations._conversationId-JEhx-f2R.js +0 -212
  236. package/apps/ui/dist/server/assets/cursor-threads._composerId-BK3iW-3e.js +0 -218
  237. package/apps/ui/dist/server/assets/json-panel-RYsxWFae.js +0 -16
  238. package/apps/ui/dist/server/assets/metadata-section-D6Lbc7D6.js +0 -54
  239. package/apps/ui/dist/server/assets/router-K0m7dK4T.js +0 -394
  240. package/apps/ui/dist/server/assets/start-bX7bXjzw.js +0 -4
  241. package/apps/ui/dist/server/assets/text-document-panel-D8JbQWAn.js +0 -23
  242. package/apps/ui/dist/server/assets/threads._threadId-LjgsGUh-.js +0 -1052
@@ -1,7 +1,10 @@
1
- import { Database } from 'bun:sqlite';
1
+ import { constants, Database } from 'bun:sqlite';
2
+ import { closeSync, openSync, readdirSync, readSync, statSync } from 'node:fs';
2
3
  import { rm } from 'node:fs/promises';
3
4
  import os from 'node:os';
4
5
  import path from 'node:path';
6
+ import { StringDecoder } from 'node:string_decoder';
7
+ import { pathToFileURL } from 'node:url';
5
8
  import type {
6
9
  DashboardSummary,
7
10
  DeleteProjectResult,
@@ -14,6 +17,7 @@ import type {
14
17
  import type { ThreadRelations, ThreadRow } from './codex-exporter-types';
15
18
  import { DEFAULT_CODEX_DIR, DEFAULT_DB_PATH } from './codex-exporter-types';
16
19
  import { getCachedParsedCodexTranscript, getThreadRolloutLoadState } from './codex-thread-cache';
20
+ import { mapWithConcurrency } from './concurrency';
17
21
  import { cleanInlineTitle, getPortablePathBasename } from './shared';
18
22
  import { runWithSqliteRetry } from './sqlite-retry';
19
23
  import { invalidateCacheByPrefix } from './ui-cache';
@@ -29,6 +33,44 @@ type DeleteProjectOptions = {
29
33
  const SQLITE_DELETE_BATCH_SIZE = 400;
30
34
  const SESSION_FILE_DELETE_CONCURRENCY = 16;
31
35
  const THREAD_LIST_IO_CONCURRENCY = 8;
36
+ const CODEX_READONLY_DB_OPEN_FLAGS = constants.SQLITE_OPEN_READONLY | constants.SQLITE_OPEN_URI;
37
+ const JSONL_READ_CHUNK_BYTES = 64 * 1024;
38
+ const SESSION_META_READ_LIMIT_BYTES = 4 * 1024 * 1024;
39
+ const THREAD_ID_PATTERN = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/iu;
40
+
41
+ type SessionIndexEntry = {
42
+ id: string;
43
+ thread_name?: string;
44
+ updated_at?: string;
45
+ };
46
+
47
+ type FallbackSessionMeta = {
48
+ agent_nickname?: string;
49
+ agent_path?: string;
50
+ agent_role?: string;
51
+ cli_version?: string;
52
+ cwd?: string;
53
+ forked_from_id?: string;
54
+ id?: string;
55
+ model_provider?: string;
56
+ parent_thread_id?: string;
57
+ source?: unknown;
58
+ thread_source?: string;
59
+ timestamp?: string;
60
+ };
61
+
62
+ type ReadFallbackThreadRowsOptions = {
63
+ includeSubagents?: boolean;
64
+ };
65
+
66
+ type FallbackRolloutStats = {
67
+ model: string | null;
68
+ tokensUsed: number;
69
+ };
70
+
71
+ const isSqliteCantOpenError = (error: unknown) => {
72
+ return (error as { code?: unknown }).code === 'SQLITE_CANTOPEN';
73
+ };
32
74
 
33
75
  const chunkValues = <T>(values: T[], chunkSize: number) => {
34
76
  const chunks: T[][] = [];
@@ -48,40 +90,20 @@ const isPromiseLike = (value: unknown): value is PromiseLike<unknown> => {
48
90
  return 'then' in value && typeof value.then === 'function';
49
91
  };
50
92
 
51
- const mapWithConcurrency = async <T, TResult>(
52
- values: T[],
53
- limit: number,
54
- mapper: (value: T, index: number) => Promise<TResult>,
55
- ) => {
56
- const results = new Array<TResult>(values.length);
57
- let nextIndex = 0;
58
-
59
- const worker = async () => {
60
- while (true) {
61
- const currentIndex = nextIndex;
62
- nextIndex += 1;
63
-
64
- if (currentIndex >= values.length) {
65
- return;
66
- }
67
-
68
- results[currentIndex] = await mapper(values[currentIndex]!, currentIndex);
69
- }
70
- };
71
-
72
- await Promise.all(Array.from({ length: Math.min(limit, values.length) }, () => worker()));
73
- return results;
74
- };
75
-
76
- const openReadonlyDb = (dbPath: string, busyTimeoutMs: number) => {
93
+ const openReadonlyDb = (dbPath: string) => {
77
94
  const db = new Database(dbPath, { readonly: true });
78
95
  try {
79
- db.exec(`PRAGMA busy_timeout = ${busyTimeoutMs}`);
96
+ db.query('SELECT name FROM sqlite_master LIMIT 1').get();
80
97
  return db;
81
98
  } catch (error) {
82
99
  db.close();
83
- throw error;
100
+ if (!isSqliteCantOpenError(error)) {
101
+ throw error;
102
+ }
84
103
  }
104
+
105
+ // Codex uses WAL mode; immutable URI reads keep Bun usable after clean shutdown removes sidecar files.
106
+ return new Database(`${pathToFileURL(dbPath).href}?immutable=1`, CODEX_READONLY_DB_OPEN_FLAGS);
85
107
  };
86
108
 
87
109
  const openWritableDb = (dbPath: string, busyTimeoutMs: number) => {
@@ -126,7 +148,7 @@ const parseJsonSafely = (value: string | null) => {
126
148
  export const withReadonlyDb = <T>(dbPath: string, callback: (db: Database) => T): T => {
127
149
  return runWithSqliteRetry({
128
150
  action: () => {
129
- const db = openReadonlyDb(dbPath, 5000);
151
+ const db = openReadonlyDb(dbPath);
130
152
  try {
131
153
  const result = callback(db);
132
154
  if (isPromiseLike(result)) {
@@ -173,12 +195,10 @@ export const resolveCodexThreadDbPath = () => {
173
195
 
174
196
  for (const candidate of candidates) {
175
197
  try {
176
- const db = runWithSqliteRetry({
177
- action: () => {
178
- return openReadonlyDb(candidate, 1500);
179
- },
180
- });
181
- db.close();
198
+ // Avoid opening candidates as a probe: Bun can make later read-only opens fail on Codex WAL databases.
199
+ if (!statSync(candidate).isFile()) {
200
+ continue;
201
+ }
182
202
  return candidate;
183
203
  } catch {}
184
204
  }
@@ -194,6 +214,399 @@ const readAllThreads = (dbPath: string): ThreadRow[] => {
194
214
  });
195
215
  };
196
216
 
217
+ const resolveCodexDirFromDbPath = (dbPath: string) => {
218
+ const dbDir = path.dirname(dbPath);
219
+ return path.basename(dbDir) === 'sqlite' ? path.dirname(dbDir) : dbDir;
220
+ };
221
+
222
+ const parseJsonlObject = <T>(line: string): T | null => {
223
+ try {
224
+ return JSON.parse(line) as T;
225
+ } catch {
226
+ return null;
227
+ }
228
+ };
229
+
230
+ const emitJsonlLine = <T>(line: string, onRecord: (record: T) => void) => {
231
+ const trimmed = line.trim();
232
+ const parsed = trimmed ? parseJsonlObject<T>(trimmed) : null;
233
+ if (parsed) {
234
+ onRecord(parsed);
235
+ }
236
+ };
237
+
238
+ const emitCompleteJsonlLines = <T>(text: string, onRecord: (record: T) => void): string => {
239
+ const lines = text.split(/\r?\n/u);
240
+ const pending = lines.pop() ?? '';
241
+ for (const line of lines) {
242
+ emitJsonlLine(line, onRecord);
243
+ }
244
+ return pending;
245
+ };
246
+
247
+ const readJsonlObjects = <T>(filePath: string, onRecord: (record: T) => void) => {
248
+ let descriptor: number | null = null;
249
+ try {
250
+ const stats = statSync(filePath);
251
+ if (!stats.isFile()) {
252
+ return;
253
+ }
254
+
255
+ descriptor = openSync(filePath, 'r');
256
+ const buffer = Buffer.alloc(JSONL_READ_CHUNK_BYTES);
257
+ const decoder = new StringDecoder('utf8');
258
+ let position = 0;
259
+ let pending = '';
260
+
261
+ while (true) {
262
+ const bytesRead = readSync(descriptor, buffer, 0, buffer.length, position);
263
+ if (bytesRead === 0) {
264
+ break;
265
+ }
266
+
267
+ position += bytesRead;
268
+ pending += decoder.write(buffer.subarray(0, bytesRead));
269
+ pending = emitCompleteJsonlLines(pending, onRecord);
270
+ }
271
+
272
+ emitJsonlLine(pending + decoder.end(), onRecord);
273
+ } catch {
274
+ return;
275
+ } finally {
276
+ if (descriptor !== null) {
277
+ closeSync(descriptor);
278
+ }
279
+ }
280
+ };
281
+
282
+ const collectJsonlObjects = <T>(filePath: string): T[] => {
283
+ const records: T[] = [];
284
+ readJsonlObjects<T>(filePath, (record) => {
285
+ records.push(record);
286
+ });
287
+ return records;
288
+ };
289
+
290
+ const readSessionIndexEntries = (codexDir: string): SessionIndexEntry[] => {
291
+ return collectJsonlObjects<SessionIndexEntry>(path.join(codexDir, 'session_index.jsonl')).filter(
292
+ (entry) => typeof entry.id === 'string' && entry.id.length > 0,
293
+ );
294
+ };
295
+
296
+ const collectSessionFilesByThreadId = (sessionsDir: string): Map<string, string> => {
297
+ const sessionFiles = new Map<string, string>();
298
+ const visit = (directory: string) => {
299
+ const entries = (() => {
300
+ try {
301
+ return readdirSync(directory, { withFileTypes: true });
302
+ } catch {
303
+ return null;
304
+ }
305
+ })();
306
+ if (!entries) {
307
+ return;
308
+ }
309
+
310
+ for (const entry of entries) {
311
+ const entryPath = path.join(directory, entry.name);
312
+ if (entry.isDirectory()) {
313
+ visit(entryPath);
314
+ continue;
315
+ }
316
+
317
+ if (!entry.isFile()) {
318
+ continue;
319
+ }
320
+
321
+ const threadId = THREAD_ID_PATTERN.exec(entry.name)?.[1];
322
+ if (threadId && !sessionFiles.has(threadId)) {
323
+ sessionFiles.set(threadId, entryPath);
324
+ }
325
+ }
326
+ };
327
+
328
+ visit(sessionsDir);
329
+ return sessionFiles;
330
+ };
331
+
332
+ const readSessionMetaLine = (sessionFile: string): string | null => {
333
+ let descriptor: number | null = null;
334
+ try {
335
+ descriptor = openSync(sessionFile, 'r');
336
+ const chunks: Buffer[] = [];
337
+ let totalBytes = 0;
338
+
339
+ while (totalBytes < SESSION_META_READ_LIMIT_BYTES) {
340
+ const buffer = Buffer.alloc(Math.min(64 * 1024, SESSION_META_READ_LIMIT_BYTES - totalBytes));
341
+ const bytesRead = readSync(descriptor, buffer, 0, buffer.length, totalBytes);
342
+ if (bytesRead === 0) {
343
+ break;
344
+ }
345
+
346
+ chunks.push(buffer.subarray(0, bytesRead));
347
+ totalBytes += bytesRead;
348
+ if (buffer.subarray(0, bytesRead).includes(10)) {
349
+ break;
350
+ }
351
+ }
352
+
353
+ const firstLine = Buffer.concat(chunks).toString('utf8').split(/\r?\n/u)[0]?.trim();
354
+ return firstLine || null;
355
+ } catch {
356
+ return null;
357
+ } finally {
358
+ if (descriptor !== null) {
359
+ closeSync(descriptor);
360
+ }
361
+ }
362
+ };
363
+
364
+ const readFallbackSessionMeta = (sessionFile: string): FallbackSessionMeta | null => {
365
+ const line = readSessionMetaLine(sessionFile);
366
+ if (!line) {
367
+ return null;
368
+ }
369
+
370
+ try {
371
+ const record = JSON.parse(line) as { payload?: FallbackSessionMeta; type?: string };
372
+ return record.type === 'session_meta' && record.payload ? record.payload : null;
373
+ } catch {
374
+ return null;
375
+ }
376
+ };
377
+
378
+ const parseIsoMs = (value: string | undefined, fallback: number) => {
379
+ if (!value) {
380
+ return fallback;
381
+ }
382
+
383
+ const parsed = Date.parse(value);
384
+ return Number.isFinite(parsed) ? parsed : fallback;
385
+ };
386
+
387
+ const stringOrNull = (value: unknown) => (typeof value === 'string' && value.length > 0 ? value : null);
388
+
389
+ const numberOrNull = (value: unknown) => (typeof value === 'number' && Number.isFinite(value) ? value : null);
390
+
391
+ const objectOrNull = (value: unknown) => {
392
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
393
+ ? (value as Record<string, unknown>)
394
+ : null;
395
+ };
396
+
397
+ const isFallbackSubagent = (sessionMeta: FallbackSessionMeta) => {
398
+ return Boolean(
399
+ sessionMeta.thread_source === 'subagent' ||
400
+ stringOrNull(sessionMeta.parent_thread_id) ||
401
+ stringOrNull(sessionMeta.forked_from_id),
402
+ );
403
+ };
404
+
405
+ const readFallbackRolloutStats = (sessionFile: string): FallbackRolloutStats => {
406
+ let model: string | null = null;
407
+ let tokensUsed = 0;
408
+
409
+ readJsonlObjects<Record<string, unknown>>(sessionFile, (record) => {
410
+ const payload = objectOrNull(record.payload);
411
+ if (!payload) {
412
+ return;
413
+ }
414
+
415
+ if (record.type === 'turn_context') {
416
+ model = stringOrNull(payload.model) ?? model;
417
+ return;
418
+ }
419
+
420
+ const payloadType = stringOrNull(payload.type);
421
+ if (payloadType === 'message' || payloadType === 'agent_message') {
422
+ model = stringOrNull(payload.model) ?? model;
423
+ return;
424
+ }
425
+
426
+ if (payloadType !== 'token_count') {
427
+ return;
428
+ }
429
+
430
+ const info = objectOrNull(payload.info);
431
+ const totalTokenUsage = objectOrNull(info?.total_token_usage);
432
+ tokensUsed = numberOrNull(totalTokenUsage?.total_tokens) ?? tokensUsed;
433
+ });
434
+
435
+ return {
436
+ model,
437
+ tokensUsed,
438
+ };
439
+ };
440
+
441
+ const buildFallbackThreadRow = (
442
+ entry: SessionIndexEntry,
443
+ sessionFile: string,
444
+ sessionMeta: FallbackSessionMeta,
445
+ rolloutStats: FallbackRolloutStats,
446
+ ): ThreadRow | null => {
447
+ const cwd = stringOrNull(sessionMeta.cwd);
448
+ if (!cwd) {
449
+ return null;
450
+ }
451
+
452
+ let mtimeMs = Date.now();
453
+ try {
454
+ mtimeMs = statSync(sessionFile).mtimeMs;
455
+ } catch {}
456
+
457
+ const updatedAtMs = parseIsoMs(entry.updated_at, mtimeMs);
458
+ const createdAtMs = parseIsoMs(sessionMeta.timestamp, updatedAtMs);
459
+ const title = entry.thread_name?.trim() || path.basename(sessionFile, '.jsonl');
460
+ const source = stringOrNull(sessionMeta.source) ?? 'session_file';
461
+
462
+ return {
463
+ agent_nickname: sessionMeta.agent_nickname ?? null,
464
+ agent_path: sessionMeta.agent_path ?? null,
465
+ agent_role: sessionMeta.agent_role ?? null,
466
+ approval_mode: 'unknown',
467
+ archived: 0,
468
+ archived_at: null,
469
+ cli_version: sessionMeta.cli_version ?? '',
470
+ created_at: Math.floor(createdAtMs / 1000),
471
+ created_at_ms: Math.floor(createdAtMs),
472
+ cwd,
473
+ first_user_message: title,
474
+ git_branch: null,
475
+ git_origin_url: null,
476
+ git_sha: null,
477
+ has_user_event: sessionMeta.thread_source === 'user' ? 1 : 0,
478
+ id: entry.id,
479
+ memory_mode: 'enabled',
480
+ model: rolloutStats.model,
481
+ model_provider: sessionMeta.model_provider ?? 'unknown',
482
+ preview: title,
483
+ reasoning_effort: null,
484
+ rollout_path: sessionFile,
485
+ sandbox_policy: '{}',
486
+ source,
487
+ thread_source: sessionMeta.thread_source ?? null,
488
+ title,
489
+ tokens_used: rolloutStats.tokensUsed,
490
+ updated_at: Math.floor(updatedAtMs / 1000),
491
+ updated_at_ms: Math.floor(updatedAtMs),
492
+ };
493
+ };
494
+
495
+ const readFallbackThreadRows = (
496
+ dbPath: string,
497
+ existingThreadIds: Set<string>,
498
+ projectName: string | null = null,
499
+ options: ReadFallbackThreadRowsOptions = {},
500
+ ): ThreadRow[] => {
501
+ const codexDir = resolveCodexDirFromDbPath(dbPath);
502
+ const sessionFilesByThreadId = collectSessionFilesByThreadId(path.join(codexDir, 'sessions'));
503
+ const fallbackThreads: ThreadRow[] = [];
504
+
505
+ for (const entry of readSessionIndexEntries(codexDir)) {
506
+ if (existingThreadIds.has(entry.id)) {
507
+ continue;
508
+ }
509
+
510
+ const sessionFile = sessionFilesByThreadId.get(entry.id);
511
+ if (!sessionFile) {
512
+ continue;
513
+ }
514
+
515
+ const sessionMeta = readFallbackSessionMeta(sessionFile);
516
+ if (!sessionMeta) {
517
+ continue;
518
+ }
519
+
520
+ if (!options.includeSubagents && isFallbackSubagent(sessionMeta)) {
521
+ continue;
522
+ }
523
+
524
+ const fallbackThread = buildFallbackThreadRow(
525
+ entry,
526
+ sessionFile,
527
+ sessionMeta,
528
+ readFallbackRolloutStats(sessionFile),
529
+ );
530
+ if (!fallbackThread || (projectName && getPortablePathBasename(fallbackThread.cwd) !== projectName)) {
531
+ continue;
532
+ }
533
+
534
+ fallbackThreads.push(fallbackThread);
535
+ }
536
+
537
+ return fallbackThreads;
538
+ };
539
+
540
+ const mergeFallbackThreadRows = (dbPath: string, threads: ThreadRow[], projectName: string | null = null) => {
541
+ const threadIds = new Set(threads.map((thread) => thread.id));
542
+ return [...threads, ...readFallbackThreadRows(dbPath, threadIds, projectName)].sort((left, right) => {
543
+ const updatedDifference = toTimestampMs(right) - toTimestampMs(left);
544
+ if (updatedDifference !== 0) {
545
+ return updatedDifference;
546
+ }
547
+
548
+ return right.id.localeCompare(left.id);
549
+ });
550
+ };
551
+
552
+ const applyRolloutActivityTimestamps = (threads: ThreadRow[]) => {
553
+ return threads
554
+ .map((thread) => {
555
+ let rolloutUpdatedAtMs = toTimestampMs(thread);
556
+ try {
557
+ rolloutUpdatedAtMs = Math.max(rolloutUpdatedAtMs, statSync(thread.rollout_path).mtimeMs);
558
+ } catch {}
559
+
560
+ if (rolloutUpdatedAtMs <= toTimestampMs(thread)) {
561
+ return thread;
562
+ }
563
+
564
+ return {
565
+ ...thread,
566
+ updated_at: Math.floor(rolloutUpdatedAtMs / 1000),
567
+ updated_at_ms: Math.floor(rolloutUpdatedAtMs),
568
+ };
569
+ })
570
+ .sort((left, right) => {
571
+ const updatedDifference = toTimestampMs(right) - toTimestampMs(left);
572
+ if (updatedDifference !== 0) {
573
+ return updatedDifference;
574
+ }
575
+
576
+ return right.id.localeCompare(left.id);
577
+ });
578
+ };
579
+
580
+ const buildDashboardRecentThreads = (threads: ThreadRow[]) => {
581
+ const bestThreadByProject = new Map<string, ThreadRow>();
582
+ for (const thread of threads) {
583
+ const project = getPortablePathBasename(thread.cwd);
584
+ if (!project) {
585
+ continue;
586
+ }
587
+
588
+ const current = bestThreadByProject.get(project);
589
+ if (!current || toTimestampMs(thread) > toTimestampMs(current)) {
590
+ bestThreadByProject.set(project, thread);
591
+ }
592
+ }
593
+
594
+ return [...bestThreadByProject.values()]
595
+ .sort((left, right) => {
596
+ const updatedDifference = toTimestampMs(right) - toTimestampMs(left);
597
+ if (updatedDifference !== 0) {
598
+ return updatedDifference;
599
+ }
600
+
601
+ return right.id.localeCompare(left.id);
602
+ })
603
+ .slice(0, 5)
604
+ .map((thread) => ({
605
+ project: getPortablePathBasename(thread.cwd),
606
+ thread: compactThreadListRow(thread),
607
+ }));
608
+ };
609
+
197
610
  const filterThreadsByProject = (threads: ThreadRow[], projectName: string | null) => {
198
611
  if (!projectName) {
199
612
  return threads;
@@ -391,7 +804,9 @@ const deleteThreadSessionFiles = async (sessionFiles: string[]) => {
391
804
  };
392
805
 
393
806
  export const listCodexProjects = (dbPath: string): ProjectSummary[] => {
394
- return mapProjectSummaries(buildProjectSummaryMap(readAllThreads(dbPath)));
807
+ return mapProjectSummaries(
808
+ buildProjectSummaryMap(applyRolloutActivityTimestamps(mergeFallbackThreadRows(dbPath, readAllThreads(dbPath)))),
809
+ );
395
810
  };
396
811
 
397
812
  type ListProjectThreadsOptions = {
@@ -411,10 +826,29 @@ export const listProjectThreads = async (
411
826
  projectName: string,
412
827
  options: ListProjectThreadsOptions = {},
413
828
  ): Promise<ThreadListEntry[]> => {
414
- const threads = filterThreadsByProject(readAllThreads(dbPath), projectName);
415
- const entries = await mapWithConcurrency(threads, THREAD_LIST_IO_CONCURRENCY, async (thread) => {
829
+ const threads = mergeFallbackThreadRows(
830
+ dbPath,
831
+ filterThreadsByProject(readAllThreads(dbPath), projectName),
832
+ projectName,
833
+ );
834
+ const activeThreads = applyRolloutActivityTimestamps(threads);
835
+ const entries = await mapWithConcurrency(activeThreads, THREAD_LIST_IO_CONCURRENCY, async (thread) => {
416
836
  const rollout = await getThreadRolloutLoadState(thread.rollout_path, options.largeTranscriptThresholdBytes);
417
837
 
838
+ if (rollout.fileSizeBytes === null) {
839
+ return {
840
+ project: projectName,
841
+ rolloutSizeBytes: null,
842
+ stats: {
843
+ deferred: false,
844
+ execCommandCount: 0,
845
+ toolCallCount: 0,
846
+ webSearchEventCount: 0,
847
+ },
848
+ thread: compactThreadListRow(thread),
849
+ };
850
+ }
851
+
418
852
  if (rollout.shouldDeferTranscriptLoad) {
419
853
  return {
420
854
  project: projectName,
@@ -450,18 +884,25 @@ export const listProjectThreads = async (
450
884
  export const getThreadBrowseData = (dbPath: string, threadId: string): ThreadBrowseData => {
451
885
  return withReadonlyDb(dbPath, (db) => {
452
886
  const existingTableNames = getExistingTableNames(db);
453
- const thread = db.query('SELECT * FROM threads WHERE id = ? LIMIT 1').get(threadId) as ThreadRow | null;
887
+ const dbThread = db.query('SELECT * FROM threads WHERE id = ? LIMIT 1').get(threadId) as ThreadRow | null;
888
+ const thread =
889
+ dbThread ??
890
+ readFallbackThreadRows(dbPath, new Set(), null, { includeSubagents: true }).find(
891
+ (fallbackThread) => fallbackThread.id === threadId,
892
+ ) ??
893
+ null;
454
894
  if (!thread) {
455
895
  throw new Error(`Thread not found: ${threadId}`);
456
896
  }
457
897
 
458
- const dynamicTools = existingTableNames.has('thread_dynamic_tools')
459
- ? (db
460
- .query(
461
- 'SELECT thread_id, position, name, description, input_schema, defer_loading, namespace FROM thread_dynamic_tools WHERE thread_id = ? ORDER BY position ASC',
462
- )
463
- .all(threadId) as Array<Record<string, number | string | null>>)
464
- : [];
898
+ const dynamicTools =
899
+ dbThread && existingTableNames.has('thread_dynamic_tools')
900
+ ? (db
901
+ .query(
902
+ 'SELECT thread_id, position, name, description, input_schema, defer_loading, namespace FROM thread_dynamic_tools WHERE thread_id = ? ORDER BY position ASC',
903
+ )
904
+ .all(threadId) as Array<Record<string, number | string | null>>)
905
+ : [];
465
906
 
466
907
  return {
467
908
  dynamicTools: dynamicTools.map((row) => parseDynamicToolRow(row)),
@@ -473,7 +914,7 @@ export const getThreadBrowseData = (dbPath: string, threadId: string): ThreadBro
473
914
  };
474
915
 
475
916
  export const getCodexDashboardSummary = (dbPath: string): DashboardSummary => {
476
- const threads = readAllThreads(dbPath);
917
+ const threads = applyRolloutActivityTimestamps(mergeFallbackThreadRows(dbPath, readAllThreads(dbPath)));
477
918
  const projects = mapProjectSummaries(buildProjectSummaryMap(threads));
478
919
  const threadsWithRelations = withReadonlyDb(dbPath, (db) => {
479
920
  if (!getExistingTableNames(db).has('thread_spawn_edges')) {
@@ -491,13 +932,7 @@ export const getCodexDashboardSummary = (dbPath: string): DashboardSummary => {
491
932
  return {
492
933
  activeThreads: threads.filter((thread) => !thread.archived).length,
493
934
  archivedThreads: threads.filter((thread) => Boolean(thread.archived)).length,
494
- recentThreads: threads
495
- .slice(0, 5)
496
- .filter((thread) => Boolean(getPortablePathBasename(thread.cwd)))
497
- .map((thread) => ({
498
- project: getPortablePathBasename(thread.cwd),
499
- thread: compactThreadListRow(thread),
500
- })),
935
+ recentThreads: buildDashboardRecentThreads(threads),
501
936
  threadsWithRelations,
502
937
  topProjectsByThreadCount: [...projects]
503
938
  .sort((left, right) => {
@@ -603,7 +1038,7 @@ export const deleteCodexProject = async (
603
1038
  };
604
1039
 
605
1040
  export const listScopedThreads = (dbPath: string, projectName: string | null): ThreadRow[] => {
606
- return filterThreadsByProject(readAllThreads(dbPath), projectName);
1041
+ return mergeFallbackThreadRows(dbPath, filterThreadsByProject(readAllThreads(dbPath), projectName), projectName);
607
1042
  };
608
1043
 
609
1044
  export const invalidateCodexUiCaches = async () => {