@cloudcli-ai/cloudcli 1.37.2 → 1.37.4

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 (453) hide show
  1. package/README.md +269 -0
  2. package/dist/assets/{abnfDiagram-N423BO3Z-C4UAoHJb.js → abnfDiagram-N423BO3Z-Dmkwx38H.js} +1 -1
  3. package/dist/assets/{arc-B8jF-ja9.js → arc-DLe9AYuO.js} +1 -1
  4. package/dist/assets/{architectureDiagram-T3A2C74G-C_Wur_CU.js → architectureDiagram-T3A2C74G-CBbbbNUW.js} +1 -1
  5. package/dist/assets/{blockDiagram-VBNYF7ZC-Cq4P1i8t.js → blockDiagram-VBNYF7ZC-CaMEaWu4.js} +1 -1
  6. package/dist/assets/{c4Diagram-5PPSVZJV-C1wG4eUj.js → c4Diagram-5PPSVZJV-DhjMgNnR.js} +1 -1
  7. package/dist/assets/channel-CWrmwsir.js +1 -0
  8. package/dist/assets/{chunk-2GRJ4B5K-B8fnBG6E.js → chunk-2GRJ4B5K-yjl3-F-i.js} +1 -1
  9. package/dist/assets/{chunk-2Q5K7J3B-WdfPooPJ.js → chunk-2Q5K7J3B-DsAYaWEP.js} +1 -1
  10. package/dist/assets/{chunk-5RXB4S5H-DhZJuG2E.js → chunk-5RXB4S5H-Bt5qmqOX.js} +1 -1
  11. package/dist/assets/{chunk-5VM5RSS4-B0rjslal.js → chunk-5VM5RSS4-yWrWVvUt.js} +1 -1
  12. package/dist/assets/{chunk-6Q2QTUOP-CG2Oj4jL.js → chunk-6Q2QTUOP-Dt66K5xb.js} +1 -1
  13. package/dist/assets/{chunk-GF5L2VYU-rrl2lRxY.js → chunk-GF5L2VYU-DA0vzaj4.js} +1 -1
  14. package/dist/assets/{chunk-JWPE2WC7-CjVbrVMA.js → chunk-JWPE2WC7-CUkbx96V.js} +1 -1
  15. package/dist/assets/{chunk-KBJHAD2P-CRzKgU8S.js → chunk-KBJHAD2P-DWKxx1WH.js} +1 -1
  16. package/dist/assets/{chunk-RYQCIY6F-DBz4GTZF.js → chunk-RYQCIY6F-BqluplN0.js} +1 -1
  17. package/dist/assets/{chunk-XXDRQBXY-wHxalsXk.js → chunk-XXDRQBXY-DelKg3Xf.js} +1 -1
  18. package/dist/assets/classDiagram-JCYQIIEL-COAGCC6w.js +1 -0
  19. package/dist/assets/classDiagram-v2-OCEON4UE-COAGCC6w.js +1 -0
  20. package/dist/assets/{cose-bilkent-JH36ORCC-s1hygY8e.js → cose-bilkent-JH36ORCC-DHV93-Em.js} +1 -1
  21. package/dist/assets/{cynefin-VYW2F7L2-C4LGV15i.js → cynefin-VYW2F7L2-B9HZgoGX.js} +1 -1
  22. package/dist/assets/{cynefinDiagram-MW4NZA55-C5uhu7B3.js → cynefinDiagram-MW4NZA55-hFjjEQg8.js} +1 -1
  23. package/dist/assets/{dagre-VZM6K2ZE-B-OBTewm.js → dagre-VZM6K2ZE-CpeVOC7V.js} +1 -1
  24. package/dist/assets/{diagram-7IWD3JNH-BjGKQxih.js → diagram-7IWD3JNH-D0H1UoRA.js} +1 -1
  25. package/dist/assets/{diagram-B4RE2ZJO-BMKz1X2L.js → diagram-B4RE2ZJO-6Y7BQUup.js} +1 -1
  26. package/dist/assets/{diagram-LBJQPF4R-DNh-GhKC.js → diagram-LBJQPF4R-C0nNOwht.js} +1 -1
  27. package/dist/assets/{diagram-Q27KOJAE-BaTG9jPT.js → diagram-Q27KOJAE-En6QlOVX.js} +1 -1
  28. package/dist/assets/{diagram-UB23O5K3-BUwo2OvJ.js → diagram-UB23O5K3-CmPiNeGD.js} +1 -1
  29. package/dist/assets/{ebnfDiagram-BXEA7PRR-DAZgfzX2.js → ebnfDiagram-BXEA7PRR-Bygzp1Qt.js} +1 -1
  30. package/dist/assets/{erDiagram-JOGREHBK-1w1CcGEb.js → erDiagram-JOGREHBK-CwScRfzl.js} +1 -1
  31. package/dist/assets/{flowDiagram-UKHOOZJN-D4EII8Jj.js → flowDiagram-UKHOOZJN-bKX-0xP6.js} +1 -1
  32. package/dist/assets/{ganttDiagram-PKOTCBZU-ChvJZAgB.js → ganttDiagram-PKOTCBZU-DtZPKXGd.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-DS77QQ5N-DtOn41S8.js → gitGraphDiagram-DS77QQ5N-CUOtdL1o.js} +1 -1
  34. package/dist/assets/index-CdjIFxM2.js +5004 -0
  35. package/dist/assets/index-DgafhGTF.css +32 -0
  36. package/dist/assets/{infoDiagram-6WML65LV-DN_PVEJZ.js → infoDiagram-6WML65LV-ciRQTS0h.js} +1 -1
  37. package/dist/assets/{ishikawaDiagram-WSZJBQD7-DbkF1kXb.js → ishikawaDiagram-WSZJBQD7-CgjhQZ84.js} +1 -1
  38. package/dist/assets/{journeyDiagram-NVQOT4AX-pJwmhddE.js → journeyDiagram-NVQOT4AX-BsbQSeIW.js} +1 -1
  39. package/dist/assets/{kanban-definition-27J2QSJJ-PPAPgNp5.js → kanban-definition-27J2QSJJ-CrwzY9Jw.js} +1 -1
  40. package/dist/assets/{linear-iPR-J9f7.js → linear-BUaCDr6l.js} +1 -1
  41. package/dist/assets/{mermaid.core-BEUL86Z6.js → mermaid.core-ClgdUU0j.js} +5 -5
  42. package/dist/assets/{mindmap-definition-FAOFIHXS-BvZoBib7.js → mindmap-definition-FAOFIHXS-BictljY1.js} +1 -1
  43. package/dist/assets/{pegDiagram-VL7TDLO6-DhYreaUd.js → pegDiagram-VL7TDLO6-BXdByy_U.js} +1 -1
  44. package/dist/assets/{pieDiagram-7S7Q4E2Y-CrOHlyD0.js → pieDiagram-7S7Q4E2Y-uZsMfkRX.js} +1 -1
  45. package/dist/assets/{quadrantDiagram-CIZ2JOQS-D4g5yH3t.js → quadrantDiagram-CIZ2JOQS-7lCv4QIj.js} +1 -1
  46. package/dist/assets/{railroadDiagram-AXF67PYL-Dj39lHlf.js → railroadDiagram-AXF67PYL-07dxIWbi.js} +1 -1
  47. package/dist/assets/{requirementDiagram-LRYGKXZP-1AS4Zgm_.js → requirementDiagram-LRYGKXZP-Cb31Yv1_.js} +1 -1
  48. package/dist/assets/{sankeyDiagram-W5VNT64P-CKlMn19P.js → sankeyDiagram-W5VNT64P-X0YPSyF9.js} +1 -1
  49. package/dist/assets/{sequenceDiagram-SI44F4Z6-B_j3qyvo.js → sequenceDiagram-SI44F4Z6-BdBxSIlP.js} +1 -1
  50. package/dist/assets/server.browser-CtX3Hdl5.js +27 -0
  51. package/dist/assets/{sizeCapture-X5ZJPWSS-Bp2Op16v.js → sizeCapture-X5ZJPWSS-DQM5xAyD.js} +1 -1
  52. package/dist/assets/{stateDiagram-OKZ733FA-C0cN6DoY.js → stateDiagram-OKZ733FA-DJ3xkLUF.js} +1 -1
  53. package/dist/assets/stateDiagram-v2-UEYNNEHI-vAaE4zNo.js +1 -0
  54. package/dist/assets/{swimlanes-SLNWSIFB-BEvXOV9b.js → swimlanes-SLNWSIFB-rjfYdW0w.js} +2 -2
  55. package/dist/assets/swimlanesDiagram-ULZ7WXOC-TV2IF4Nq.js +8 -0
  56. package/dist/assets/{timeline-definition-Z64GVDOM-zdJQinvZ.js → timeline-definition-Z64GVDOM-DZMra5do.js} +1 -1
  57. package/dist/assets/{vendor-codemirror-OwyKSvPE.js → vendor-codemirror-CYrSt7-U.js} +18 -18
  58. package/dist/assets/{vennDiagram-T6HMQDX7-DuF1uZMY.js → vennDiagram-T6HMQDX7-BevD8uu0.js} +1 -1
  59. package/dist/assets/{wardleyDiagram-T6FBY63Y-B9AlLQup.js → wardleyDiagram-T6FBY63Y-DsIyXB7z.js} +1 -1
  60. package/dist/assets/{xychartDiagram-ELKLHX3M-DTJX48Qg.js → xychartDiagram-ELKLHX3M-G3PlF3ko.js} +1 -1
  61. package/dist/index.html +3 -3
  62. package/dist-server/server/index.js +11 -4
  63. package/dist-server/server/index.js.map +1 -1
  64. package/dist-server/server/modules/agent/agent.module.js +9 -2
  65. package/dist-server/server/modules/agent/agent.module.js.map +1 -1
  66. package/dist-server/server/modules/agent/agent.routes.js +177 -130
  67. package/dist-server/server/modules/agent/agent.routes.js.map +1 -1
  68. package/dist-server/server/modules/agent/tests/agent-run-registration.test.js +306 -0
  69. package/dist-server/server/modules/agent/tests/agent-run-registration.test.js.map +1 -0
  70. package/dist-server/server/modules/agent/tests/agent.routes.test.js +48 -2
  71. package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -1
  72. package/dist-server/server/modules/browser-use/browser-use.routes.js +39 -33
  73. package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -1
  74. package/dist-server/server/modules/database/connection.js +1 -1
  75. package/dist-server/server/modules/database/connection.js.map +1 -1
  76. package/dist-server/server/modules/database/index.js +5 -0
  77. package/dist-server/server/modules/database/index.js.map +1 -1
  78. package/dist-server/server/modules/database/migrations.js +45 -1
  79. package/dist-server/server/modules/database/migrations.js.map +1 -1
  80. package/dist-server/server/modules/database/repositories/provider-models.js +60 -6
  81. package/dist-server/server/modules/database/repositories/provider-models.js.map +1 -1
  82. package/dist-server/server/modules/database/repositories/scheduled-messages.db.js +71 -0
  83. package/dist-server/server/modules/database/repositories/scheduled-messages.db.js.map +1 -0
  84. package/dist-server/server/modules/database/repositories/session-drafts.db.js +105 -0
  85. package/dist-server/server/modules/database/repositories/session-drafts.db.js.map +1 -0
  86. package/dist-server/server/modules/database/repositories/sessions.db.js +144 -5
  87. package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
  88. package/dist-server/server/modules/database/repositories/user-preferences.db.js +65 -0
  89. package/dist-server/server/modules/database/repositories/user-preferences.db.js.map +1 -0
  90. package/dist-server/server/modules/database/schema.js +99 -0
  91. package/dist-server/server/modules/database/schema.js.map +1 -1
  92. package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +99 -0
  93. package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js.map +1 -1
  94. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +39 -0
  95. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
  96. package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js +105 -0
  97. package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js.map +1 -0
  98. package/dist-server/server/modules/file-tree/file-tree.module.js +2 -1
  99. package/dist-server/server/modules/file-tree/file-tree.module.js.map +1 -1
  100. package/dist-server/server/modules/file-tree/file-tree.service.js +44 -7
  101. package/dist-server/server/modules/file-tree/file-tree.service.js.map +1 -1
  102. package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js +126 -1
  103. package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -1
  104. package/dist-server/server/modules/git/git-branch-diff.service.js +142 -0
  105. package/dist-server/server/modules/git/git-branch-diff.service.js.map +1 -0
  106. package/dist-server/server/modules/git/git-branch.service.js.map +1 -1
  107. package/dist-server/server/modules/git/git.routes.js +92 -5
  108. package/dist-server/server/modules/git/git.routes.js.map +1 -1
  109. package/dist-server/server/modules/git/tests/git-branch-diff.service.test.js +160 -0
  110. package/dist-server/server/modules/git/tests/git-branch-diff.service.test.js.map +1 -0
  111. package/dist-server/server/modules/projects/project-clone.routes.js +108 -0
  112. package/dist-server/server/modules/projects/project-clone.routes.js.map +1 -0
  113. package/dist-server/server/modules/projects/projects.routes.js +13 -66
  114. package/dist-server/server/modules/projects/projects.routes.js.map +1 -1
  115. package/dist-server/server/modules/projects/services/project-clone-request.service.js +36 -0
  116. package/dist-server/server/modules/projects/services/project-clone-request.service.js.map +1 -0
  117. package/dist-server/server/modules/projects/services/project-clone.service.js +86 -32
  118. package/dist-server/server/modules/projects/services/project-clone.service.js.map +1 -1
  119. package/dist-server/server/modules/projects/services/project-delete.service.js +12 -1
  120. package/dist-server/server/modules/projects/services/project-delete.service.js.map +1 -1
  121. package/dist-server/server/modules/projects/services/project-star.service.js +2 -2
  122. package/dist-server/server/modules/projects/services/project-star.service.js.map +1 -1
  123. package/dist-server/server/modules/projects/tests/project-clone.routes.test.js +191 -0
  124. package/dist-server/server/modules/projects/tests/project-clone.routes.test.js.map +1 -0
  125. package/dist-server/server/modules/projects/tests/project-clone.service.test.js +275 -0
  126. package/dist-server/server/modules/projects/tests/project-clone.service.test.js.map +1 -1
  127. package/dist-server/server/modules/providers/index.js +3 -0
  128. package/dist-server/server/modules/providers/index.js.map +1 -1
  129. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +62 -9
  130. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
  131. package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js +46 -0
  132. package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js.map +1 -0
  133. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +31 -12
  134. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
  135. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +472 -53
  136. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -1
  137. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +38 -11
  138. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -1
  139. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +1007 -60
  140. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
  141. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +4 -1
  142. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
  143. package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
  144. package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
  145. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js +204 -0
  146. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js.map +1 -0
  147. package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js +32 -0
  148. package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js.map +1 -0
  149. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +47 -0
  150. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
  151. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js +71 -53
  152. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js.map +1 -1
  153. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +8 -0
  154. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
  155. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +1621 -431
  156. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
  157. package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
  158. package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
  159. package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js +9 -2
  160. package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js.map +1 -1
  161. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js +16 -8
  162. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js.map +1 -1
  163. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
  164. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +274 -9
  165. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
  166. package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js +5 -2
  167. package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js.map +1 -1
  168. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +19 -2
  169. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
  170. package/dist-server/server/modules/providers/provider.routes.js +134 -2
  171. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  172. package/dist-server/server/modules/providers/services/claude-cli-liveness.service.js +100 -0
  173. package/dist-server/server/modules/providers/services/claude-cli-liveness.service.js.map +1 -0
  174. package/dist-server/server/modules/providers/services/claude-settings-permissions.service.js +97 -0
  175. package/dist-server/server/modules/providers/services/claude-settings-permissions.service.js.map +1 -0
  176. package/dist-server/server/modules/providers/services/provider-capabilities.service.js +23 -1
  177. package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
  178. package/dist-server/server/modules/providers/services/provider-models.service.js +59 -5
  179. package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
  180. package/dist-server/server/modules/providers/services/provider-runtime.service.js +8 -0
  181. package/dist-server/server/modules/providers/services/provider-runtime.service.js.map +1 -1
  182. package/dist-server/server/modules/providers/services/provider-token-usage.service.js +139 -35
  183. package/dist-server/server/modules/providers/services/provider-token-usage.service.js.map +1 -1
  184. package/dist-server/server/modules/providers/services/session-conversations-search.service.js +2 -4
  185. package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
  186. package/dist-server/server/modules/providers/services/session-history-cache.service.js +91 -0
  187. package/dist-server/server/modules/providers/services/session-history-cache.service.js.map +1 -0
  188. package/dist-server/server/modules/providers/services/session-synchronizer.service.js +105 -32
  189. package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
  190. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +3 -60
  191. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
  192. package/dist-server/server/modules/providers/services/sessions.service.js +252 -12
  193. package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
  194. package/dist-server/server/modules/providers/tests/claude-auth.test.js +183 -0
  195. package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -1
  196. package/dist-server/server/modules/providers/tests/claude-background-work.test.js +172 -0
  197. package/dist-server/server/modules/providers/tests/claude-background-work.test.js.map +1 -0
  198. package/dist-server/server/modules/providers/tests/claude-compaction.test.js +84 -0
  199. package/dist-server/server/modules/providers/tests/claude-compaction.test.js.map +1 -0
  200. package/dist-server/server/modules/providers/tests/claude-message-model.test.js +123 -0
  201. package/dist-server/server/modules/providers/tests/claude-message-model.test.js.map +1 -0
  202. package/dist-server/server/modules/providers/tests/claude-models.test.js +39 -0
  203. package/dist-server/server/modules/providers/tests/claude-models.test.js.map +1 -0
  204. package/dist-server/server/modules/providers/tests/claude-runtime-hold.test.js +265 -0
  205. package/dist-server/server/modules/providers/tests/claude-runtime-hold.test.js.map +1 -0
  206. package/dist-server/server/modules/providers/tests/claude-sessions.test.js +1815 -33
  207. package/dist-server/server/modules/providers/tests/claude-sessions.test.js.map +1 -1
  208. package/dist-server/server/modules/providers/tests/claude-settings-permissions.test.js +228 -0
  209. package/dist-server/server/modules/providers/tests/claude-settings-permissions.test.js.map +1 -0
  210. package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js +16 -0
  211. package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js.map +1 -0
  212. package/dist-server/server/modules/providers/tests/claude-token-budget.test.js +85 -0
  213. package/dist-server/server/modules/providers/tests/claude-token-budget.test.js.map +1 -0
  214. package/dist-server/server/modules/providers/tests/codex-message-editing.test.js +338 -0
  215. package/dist-server/server/modules/providers/tests/codex-message-editing.test.js.map +1 -0
  216. package/dist-server/server/modules/providers/tests/codex-models.test.js +25 -0
  217. package/dist-server/server/modules/providers/tests/codex-models.test.js.map +1 -0
  218. package/dist-server/server/modules/providers/tests/codex-runtime.test.js +83 -0
  219. package/dist-server/server/modules/providers/tests/codex-runtime.test.js.map +1 -0
  220. package/dist-server/server/modules/providers/tests/codex-session-fork.test.js +54 -0
  221. package/dist-server/server/modules/providers/tests/codex-session-fork.test.js.map +1 -0
  222. package/dist-server/server/modules/providers/tests/codex-sessions.test.js +354 -15
  223. package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
  224. package/dist-server/server/modules/providers/tests/cursor-auth.test.js +167 -0
  225. package/dist-server/server/modules/providers/tests/cursor-auth.test.js.map +1 -0
  226. package/dist-server/server/modules/providers/tests/opencode-models.test.js +196 -4
  227. package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
  228. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +19 -1
  229. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
  230. package/dist-server/server/modules/providers/tests/provider-capabilities.service.test.js +30 -0
  231. package/dist-server/server/modules/providers/tests/provider-capabilities.service.test.js.map +1 -0
  232. package/dist-server/server/modules/providers/tests/provider-models.service.test.js +79 -1
  233. package/dist-server/server/modules/providers/tests/provider-models.service.test.js.map +1 -1
  234. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +164 -1
  235. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -1
  236. package/dist-server/server/modules/providers/tests/provider.routes.test.js +190 -1
  237. package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -1
  238. package/dist-server/server/modules/providers/tests/session-fork.test.js +113 -0
  239. package/dist-server/server/modules/providers/tests/session-fork.test.js.map +1 -0
  240. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js +139 -0
  241. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js.map +1 -0
  242. package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js +90 -0
  243. package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js.map +1 -0
  244. package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js +122 -0
  245. package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js.map +1 -0
  246. package/dist-server/server/modules/providers/tests/sessions.service.test.js +219 -1
  247. package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -1
  248. package/dist-server/server/modules/providers/tests/skills.test.js +15 -3
  249. package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
  250. package/dist-server/server/modules/scheduled-messages/index.js +5 -0
  251. package/dist-server/server/modules/scheduled-messages/index.js.map +1 -0
  252. package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js +47 -0
  253. package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js.map +1 -0
  254. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js +175 -0
  255. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js.map +1 -0
  256. package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js +95 -0
  257. package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js.map +1 -0
  258. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js +290 -0
  259. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js.map +1 -0
  260. package/dist-server/server/modules/taskmaster/taskmaster.routes.js +23 -23
  261. package/dist-server/server/modules/taskmaster/taskmaster.routes.js.map +1 -1
  262. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js +48 -0
  263. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js.map +1 -1
  264. package/dist-server/server/modules/user/tests/user.service.test.js +70 -0
  265. package/dist-server/server/modules/user/tests/user.service.test.js.map +1 -1
  266. package/dist-server/server/modules/user/user.module.js +10 -1
  267. package/dist-server/server/modules/user/user.module.js.map +1 -1
  268. package/dist-server/server/modules/user/user.routes.js +44 -0
  269. package/dist-server/server/modules/user/user.routes.js.map +1 -1
  270. package/dist-server/server/modules/user/user.service.js +75 -0
  271. package/dist-server/server/modules/user/user.service.js.map +1 -1
  272. package/dist-server/server/modules/voice/tests/voice.service.test.js +3 -2
  273. package/dist-server/server/modules/voice/tests/voice.service.test.js.map +1 -1
  274. package/dist-server/server/modules/websocket/index.js +7 -0
  275. package/dist-server/server/modules/websocket/index.js.map +1 -1
  276. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +37 -49
  277. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -1
  278. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +35 -5
  279. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -1
  280. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +204 -10
  281. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
  282. package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js +94 -0
  283. package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js.map +1 -0
  284. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +236 -20
  285. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
  286. package/dist-server/server/modules/websocket/tests/chat-background-subscribe.test.js +137 -0
  287. package/dist-server/server/modules/websocket/tests/chat-background-subscribe.test.js.map +1 -0
  288. package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js +263 -0
  289. package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js.map +1 -0
  290. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +29 -3
  291. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -1
  292. package/dist-server/server/modules/websocket/tests/chat-stop-task.test.js +96 -0
  293. package/dist-server/server/modules/websocket/tests/chat-stop-task.test.js.map +1 -0
  294. package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js +106 -0
  295. package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js.map +1 -0
  296. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js +500 -0
  297. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js.map +1 -1
  298. package/dist-server/server/shared/claude-cli-path.js +22 -5
  299. package/dist-server/server/shared/claude-cli-path.js.map +1 -1
  300. package/dist-server/server/shared/index.js +3 -0
  301. package/dist-server/server/shared/index.js.map +1 -0
  302. package/dist-server/server/shared/message-unification.js +376 -0
  303. package/dist-server/server/shared/message-unification.js.map +1 -0
  304. package/dist-server/server/shared/tests/claude-cli-path.test.js +45 -1
  305. package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -1
  306. package/dist-server/server/shared/tests/message-unification.test.js +174 -0
  307. package/dist-server/server/shared/tests/message-unification.test.js.map +1 -0
  308. package/dist-server/server/shared/tests/read-only-roots.test.js +124 -0
  309. package/dist-server/server/shared/tests/read-only-roots.test.js.map +1 -0
  310. package/dist-server/server/shared/tests/strip-ansi-sequences.test.js +31 -0
  311. package/dist-server/server/shared/tests/strip-ansi-sequences.test.js.map +1 -0
  312. package/dist-server/server/shared/utils.js +160 -0
  313. package/dist-server/server/shared/utils.js.map +1 -1
  314. package/electron/desktopWindow.js +6 -0
  315. package/electron/viewHost.js +25 -2
  316. package/package.json +20 -17
  317. package/server/index.ts +16 -5
  318. package/server/modules/agent/agent.module.ts +11 -1
  319. package/server/modules/agent/agent.routes.ts +200 -142
  320. package/server/modules/agent/tests/agent-run-registration.test.ts +348 -0
  321. package/server/modules/agent/tests/agent.routes.test.ts +51 -2
  322. package/server/modules/browser-use/browser-use.routes.ts +96 -33
  323. package/server/modules/database/connection.ts +2 -1
  324. package/server/modules/database/index.ts +10 -0
  325. package/server/modules/database/migrations.ts +51 -0
  326. package/server/modules/database/repositories/provider-models.ts +79 -6
  327. package/server/modules/database/repositories/scheduled-messages.db.ts +127 -0
  328. package/server/modules/database/repositories/session-drafts.db.ts +171 -0
  329. package/server/modules/database/repositories/sessions.db.ts +204 -3
  330. package/server/modules/database/repositories/user-preferences.db.ts +80 -0
  331. package/server/modules/database/schema.ts +103 -0
  332. package/server/modules/database/tests/provider-models.db.integration.test.ts +113 -0
  333. package/server/modules/database/tests/sessions.db.integration.test.ts +52 -0
  334. package/server/modules/database/tests/user-preferences.db.integration.test.ts +135 -0
  335. package/server/modules/file-tree/file-tree.module.ts +2 -1
  336. package/server/modules/file-tree/file-tree.service.ts +49 -7
  337. package/server/modules/file-tree/tests/file-tree.service.test.ts +163 -1
  338. package/server/modules/git/git-branch-diff.service.ts +204 -0
  339. package/server/modules/git/git-branch.service.ts +2 -12
  340. package/server/modules/git/git.routes.ts +100 -6
  341. package/server/modules/git/tests/git-branch-diff.service.test.ts +210 -0
  342. package/server/modules/projects/project-clone.routes.ts +142 -0
  343. package/server/modules/projects/projects.routes.ts +16 -81
  344. package/server/modules/projects/services/project-clone-request.service.ts +56 -0
  345. package/server/modules/projects/services/project-clone.service.ts +102 -34
  346. package/server/modules/projects/services/project-delete.service.ts +13 -1
  347. package/server/modules/projects/services/project-star.service.ts +2 -2
  348. package/server/modules/projects/tests/project-clone.routes.test.ts +246 -0
  349. package/server/modules/projects/tests/project-clone.service.test.ts +376 -0
  350. package/server/modules/providers/index.ts +4 -0
  351. package/server/modules/providers/list/claude/claude-auth.provider.ts +81 -11
  352. package/server/modules/providers/list/claude/claude-fork.provider.ts +57 -0
  353. package/server/modules/providers/list/claude/claude-models.provider.ts +37 -17
  354. package/server/modules/providers/list/claude/claude-runtime.provider.js +510 -54
  355. package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +40 -14
  356. package/server/modules/providers/list/claude/claude-sessions.provider.ts +1195 -63
  357. package/server/modules/providers/list/claude/claude-skills.provider.ts +4 -1
  358. package/server/modules/providers/list/claude/claude.provider.ts +3 -0
  359. package/server/modules/providers/list/codex/codex-app-server.client.ts +243 -0
  360. package/server/modules/providers/list/codex/codex-fork.provider.ts +40 -0
  361. package/server/modules/providers/list/codex/codex-models.provider.ts +47 -0
  362. package/server/modules/providers/list/codex/{codex-runtime.provider.js → codex-runtime.provider.ts} +105 -70
  363. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +9 -0
  364. package/server/modules/providers/list/codex/codex-sessions.provider.ts +1847 -455
  365. package/server/modules/providers/list/codex/codex.provider.ts +3 -0
  366. package/server/modules/providers/list/cursor/cursor-auth.provider.ts +10 -2
  367. package/server/modules/providers/list/cursor/cursor-runtime.provider.js +17 -8
  368. package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +0 -1
  369. package/server/modules/providers/list/opencode/opencode-models.provider.ts +297 -9
  370. package/server/modules/providers/list/opencode/opencode-runtime.provider.js +5 -2
  371. package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +30 -2
  372. package/server/modules/providers/provider.routes.ts +162 -2
  373. package/server/modules/providers/services/claude-cli-liveness.service.ts +121 -0
  374. package/server/modules/providers/services/claude-settings-permissions.service.ts +127 -0
  375. package/server/modules/providers/services/provider-capabilities.service.ts +40 -1
  376. package/server/modules/providers/services/provider-models.service.ts +71 -4
  377. package/server/modules/providers/services/provider-runtime.service.ts +11 -0
  378. package/server/modules/providers/services/provider-token-usage.service.ts +166 -39
  379. package/server/modules/providers/services/session-conversations-search.service.ts +2 -5
  380. package/server/modules/providers/services/session-history-cache.service.ts +150 -0
  381. package/server/modules/providers/services/session-synchronizer.service.ts +125 -38
  382. package/server/modules/providers/services/sessions-watcher.service.ts +3 -64
  383. package/server/modules/providers/services/sessions.service.ts +309 -17
  384. package/server/modules/providers/tests/claude-auth.test.ts +208 -0
  385. package/server/modules/providers/tests/claude-background-work.test.ts +213 -0
  386. package/server/modules/providers/tests/claude-compaction.test.ts +101 -0
  387. package/server/modules/providers/tests/claude-message-model.test.ts +160 -0
  388. package/server/modules/providers/tests/claude-models.test.ts +87 -0
  389. package/server/modules/providers/tests/claude-runtime-hold.test.ts +309 -0
  390. package/server/modules/providers/tests/claude-sessions.test.ts +2167 -37
  391. package/server/modules/providers/tests/claude-settings-permissions.test.ts +271 -0
  392. package/server/modules/providers/tests/claude-subagent-echo.test.ts +25 -0
  393. package/server/modules/providers/tests/claude-token-budget.test.ts +104 -0
  394. package/server/modules/providers/tests/codex-message-editing.test.ts +419 -0
  395. package/server/modules/providers/tests/codex-models.test.ts +37 -0
  396. package/server/modules/providers/tests/codex-runtime.test.ts +94 -0
  397. package/server/modules/providers/tests/codex-session-fork.test.ts +59 -0
  398. package/server/modules/providers/tests/codex-sessions.test.ts +396 -15
  399. package/server/modules/providers/tests/cursor-auth.test.ts +187 -0
  400. package/server/modules/providers/tests/opencode-models.test.ts +266 -8
  401. package/server/modules/providers/tests/opencode-sessions.test.ts +40 -1
  402. package/server/modules/providers/tests/provider-capabilities.service.test.ts +37 -0
  403. package/server/modules/providers/tests/provider-models.service.test.ts +99 -1
  404. package/server/modules/providers/tests/provider-token-usage.service.test.ts +188 -1
  405. package/server/modules/providers/tests/provider.routes.test.ts +230 -1
  406. package/server/modules/providers/tests/session-fork.test.ts +173 -0
  407. package/server/modules/providers/tests/session-history-cache.service.test.ts +165 -0
  408. package/server/modules/providers/tests/session-orphan-prune.test.ts +107 -0
  409. package/server/modules/providers/tests/session-synchronizer-coalescing.test.ts +136 -0
  410. package/server/modules/providers/tests/sessions.service.test.ts +303 -1
  411. package/server/modules/providers/tests/skills.test.ts +21 -5
  412. package/server/modules/scheduled-messages/index.ts +8 -0
  413. package/server/modules/scheduled-messages/scheduled-messages.routes.ts +68 -0
  414. package/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.ts +216 -0
  415. package/server/modules/scheduled-messages/services/scheduled-messages.service.ts +121 -0
  416. package/server/modules/scheduled-messages/tests/scheduled-messages.test.ts +354 -0
  417. package/server/modules/taskmaster/taskmaster.routes.ts +23 -33
  418. package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +57 -0
  419. package/server/modules/user/tests/user.service.test.ts +90 -0
  420. package/server/modules/user/user.module.ts +10 -1
  421. package/server/modules/user/user.routes.ts +44 -0
  422. package/server/modules/user/user.service.ts +105 -0
  423. package/server/modules/voice/tests/voice.service.test.ts +3 -2
  424. package/server/modules/websocket/README.md +4 -2
  425. package/server/modules/websocket/index.ts +8 -0
  426. package/server/modules/websocket/services/chat-run-registry.service.ts +45 -54
  427. package/server/modules/websocket/services/chat-session-writer.service.ts +36 -6
  428. package/server/modules/websocket/services/chat-websocket.service.ts +302 -16
  429. package/server/modules/websocket/services/session-upsert-broadcast.service.ts +108 -0
  430. package/server/modules/websocket/services/shell-websocket.service.ts +276 -22
  431. package/server/modules/websocket/tests/chat-background-subscribe.test.ts +167 -0
  432. package/server/modules/websocket/tests/chat-edit-send.test.ts +309 -0
  433. package/server/modules/websocket/tests/chat-run-registry.test.ts +34 -3
  434. package/server/modules/websocket/tests/chat-stop-task.test.ts +127 -0
  435. package/server/modules/websocket/tests/session-upsert-broadcast.test.ts +138 -0
  436. package/server/modules/websocket/tests/shell-websocket.service.test.ts +629 -0
  437. package/server/shared/claude-cli-path.ts +24 -6
  438. package/server/shared/index.ts +3 -0
  439. package/server/shared/interfaces.ts +91 -0
  440. package/server/shared/message-unification.ts +449 -0
  441. package/server/shared/tests/claude-cli-path.test.ts +58 -1
  442. package/server/shared/tests/message-unification.test.ts +218 -0
  443. package/server/shared/tests/read-only-roots.test.ts +139 -0
  444. package/server/shared/tests/strip-ansi-sequences.test.ts +42 -0
  445. package/server/shared/types.ts +382 -7
  446. package/server/shared/utils.ts +176 -0
  447. package/dist/assets/channel-_falYbU1.js +0 -1
  448. package/dist/assets/classDiagram-JCYQIIEL-Dn-3CfpR.js +0 -1
  449. package/dist/assets/classDiagram-v2-OCEON4UE-Dn-3CfpR.js +0 -1
  450. package/dist/assets/index-CzPLBvXT.css +0 -32
  451. package/dist/assets/index-DFkBBuSA.js +0 -1549
  452. package/dist/assets/stateDiagram-v2-UEYNNEHI-BvBxUUrZ.js +0 -1
  453. package/dist/assets/swimlanesDiagram-ULZ7WXOC-CsE-x4YK.js +0 -8
@@ -13,6 +13,8 @@ type SessionRow = {
13
13
  model: string | null;
14
14
  /** Reasoning effort this session runs with; NULL until the app records one. */
15
15
  effort: string | null;
16
+ /** The app session this one was branched from; NULL unless it is a fork. */
17
+ forked_from_session_id: string | null;
16
18
  isArchived: number;
17
19
  created_at: string;
18
20
  updated_at: string;
@@ -24,7 +26,7 @@ type RecentSessionsPage = {
24
26
  };
25
27
 
26
28
  const SESSION_ROW_COLUMNS =
27
- 'session_id, provider, provider_session_id, project_path, jsonl_path, custom_name, model, effort, isArchived, created_at, updated_at';
29
+ 'session_id, provider, provider_session_id, project_path, jsonl_path, custom_name, model, effort, forked_from_session_id, isArchived, created_at, updated_at';
28
30
 
29
31
  const SQLITE_UTC_TIMESTAMP_REGEX = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
30
32
 
@@ -111,7 +113,7 @@ export const sessionsDb = {
111
113
  updated_at = COALESCE(?, CURRENT_TIMESTAMP),
112
114
  project_path = ?,
113
115
  jsonl_path = ?,
114
- isArchived = 0,
116
+ isArchived = CASE WHEN ? IS NULL OR julianday(?) > julianday(updated_at) THEN 0 ELSE isArchived END,
115
117
  custom_name = CASE
116
118
  WHEN session_id <> provider_session_id AND custom_name IS NOT NULL THEN custom_name
117
119
  ELSE COALESCE(?, custom_name)
@@ -122,6 +124,8 @@ export const sessionsDb = {
122
124
  updatedAtValue,
123
125
  normalizedProjectPath,
124
126
  jsonlPath ?? null,
127
+ updatedAtValue,
128
+ updatedAtValue,
125
129
  customName ?? null,
126
130
  existing.session_id
127
131
  );
@@ -141,7 +145,7 @@ export const sessionsDb = {
141
145
  updated_at = excluded.updated_at,
142
146
  project_path = excluded.project_path,
143
147
  jsonl_path = excluded.jsonl_path,
144
- isArchived = 0,
148
+ isArchived = CASE WHEN ? IS NULL OR julianday(excluded.updated_at) > julianday(sessions.updated_at) THEN 0 ELSE sessions.isArchived END,
145
149
  custom_name = CASE
146
150
  WHEN sessions.session_id <> sessions.provider_session_id AND sessions.custom_name IS NOT NULL
147
151
  THEN sessions.custom_name
@@ -155,6 +159,7 @@ export const sessionsDb = {
155
159
  normalizedProjectPath,
156
160
  jsonlPath ?? null,
157
161
  createdAtValue,
162
+ updatedAtValue,
158
163
  updatedAtValue
159
164
  );
160
165
 
@@ -189,6 +194,55 @@ export const sessionsDb = {
189
194
  return sessionId;
190
195
  },
191
196
 
197
+ /**
198
+ * Inserts a session that already has its provider artifact on disk.
199
+ *
200
+ * Unlike `createAppSession` this writes `provider_session_id` and
201
+ * `jsonl_path` immediately, because a fork's transcript file exists before
202
+ * the row does — and the filesystem watcher would otherwise index it as an
203
+ * unrelated session under its own id.
204
+ */
205
+ createForkedSession(input: {
206
+ sessionId: string;
207
+ provider: string;
208
+ projectPath: string;
209
+ customName: string | null;
210
+ providerSessionId: string;
211
+ jsonlPath: string;
212
+ forkedFromSessionId: string;
213
+ model: string | null;
214
+ effort: string | null;
215
+ }): string {
216
+ const db = getConnection();
217
+ const normalizedProjectPath = normalizeProjectPathForProvider(input.provider, input.projectPath);
218
+
219
+ projectsDb.createProjectPath(normalizedProjectPath);
220
+
221
+ // The watcher may already have created a row for the new transcript. Its
222
+ // id is the provider-native one, which is what this row claims, so replace
223
+ // it rather than leaving two sidebar entries for one conversation.
224
+ db.transaction(() => {
225
+ db.prepare('DELETE FROM sessions WHERE session_id = ? AND session_id <> ?')
226
+ .run(input.providerSessionId, input.sessionId);
227
+ db.prepare(
228
+ `INSERT INTO sessions (session_id, provider, provider_session_id, custom_name, project_path, jsonl_path, model, effort, forked_from_session_id, isArchived, created_at, updated_at)
229
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`
230
+ ).run(
231
+ input.sessionId,
232
+ input.provider,
233
+ input.providerSessionId,
234
+ input.customName,
235
+ normalizedProjectPath,
236
+ input.jsonlPath,
237
+ input.model,
238
+ input.effort,
239
+ input.forkedFromSessionId,
240
+ );
241
+ })();
242
+
243
+ return input.sessionId;
244
+ },
245
+
192
246
  /**
193
247
  * Records the provider-native session id for one app-allocated session.
194
248
  *
@@ -235,6 +289,125 @@ export const sessionsDb = {
235
289
  merge();
236
290
  },
237
291
 
292
+ /**
293
+ * Moves one session onto a different provider session and transcript.
294
+ *
295
+ * Only editing a message on a provider that has to branch to rewind (Codex)
296
+ * does this — an ordinary run keeps the same provider session for its whole
297
+ * life. `assignProviderSessionId` cannot be used for it: that one keeps the
298
+ * existing `jsonl_path` on purpose, so a session repointed with it would
299
+ * claim the new thread while still reading the old transcript.
300
+ *
301
+ * The watcher may already have indexed the new transcript under its own id.
302
+ * That row is the same conversation this one is about to become, so it is
303
+ * replaced rather than left behind as a second sidebar entry.
304
+ */
305
+ repointSessionToProviderSession(
306
+ sessionId: string,
307
+ input: { providerSessionId: string; jsonlPath: string },
308
+ ): void {
309
+ const db = getConnection();
310
+
311
+ db.transaction(() => {
312
+ db.prepare('DELETE FROM sessions WHERE session_id = ? AND session_id <> ?')
313
+ .run(input.providerSessionId, sessionId);
314
+ db.prepare(
315
+ `UPDATE sessions SET
316
+ provider_session_id = ?,
317
+ jsonl_path = ?,
318
+ updated_at = CURRENT_TIMESTAMP
319
+ WHERE session_id = ?`
320
+ ).run(input.providerSessionId, input.jsonlPath, sessionId);
321
+ })();
322
+ },
323
+
324
+ /**
325
+ * Detaches a session from its provider session so the next run starts a new
326
+ * one.
327
+ *
328
+ * Used when an edit replaces the very first prompt: there is no conversation
329
+ * left to branch from, so the session starts over instead.
330
+ */
331
+ detachProviderSession(sessionId: string): void {
332
+ const db = getConnection();
333
+ db.prepare(
334
+ `UPDATE sessions SET
335
+ provider_session_id = NULL,
336
+ jsonl_path = NULL,
337
+ updated_at = CURRENT_TIMESTAMP
338
+ WHERE session_id = ?`
339
+ ).run(sessionId);
340
+ },
341
+
342
+ /**
343
+ * Records that a session has left a provider session behind for good.
344
+ *
345
+ * The transcript stays on disk, which is deliberate — the abandoned attempt
346
+ * is recoverable — but the indexer must not offer it back, and on a session
347
+ * discovered from disk (whose app id *is* the provider id) rediscovering it
348
+ * would repoint the row at the conversation the user edited away from.
349
+ */
350
+ markProviderSessionSuperseded(input: {
351
+ providerSessionId: string;
352
+ provider: string;
353
+ sessionId: string;
354
+ jsonlPath: string | null;
355
+ }): void {
356
+ const db = getConnection();
357
+ db.prepare(
358
+ `INSERT INTO superseded_provider_sessions (provider_session_id, provider, session_id, jsonl_path)
359
+ VALUES (?, ?, ?, ?)
360
+ ON CONFLICT(provider_session_id, provider) DO UPDATE SET
361
+ session_id = excluded.session_id,
362
+ jsonl_path = excluded.jsonl_path,
363
+ created_at = CURRENT_TIMESTAMP`
364
+ ).run(input.providerSessionId, input.provider, input.sessionId, input.jsonlPath);
365
+ },
366
+
367
+ isProviderSessionSuperseded(providerSessionId: string, provider: string): boolean {
368
+ const db = getConnection();
369
+ const row = db
370
+ .prepare(
371
+ `SELECT 1 AS found FROM superseded_provider_sessions
372
+ WHERE provider_session_id = ? AND provider = ?
373
+ LIMIT 1`
374
+ )
375
+ .get(providerSessionId, provider) as { found: number } | undefined;
376
+
377
+ return Boolean(row);
378
+ },
379
+
380
+ /**
381
+ * Transcripts one session has left behind, for the caller that deletes a
382
+ * conversation from disk.
383
+ *
384
+ * A conversation edited more than once has lived in more than one file, and
385
+ * the session row only ever points at the newest.
386
+ */
387
+ getSupersededTranscriptPaths(sessionId: string): string[] {
388
+ const db = getConnection();
389
+ const rows = db
390
+ .prepare(
391
+ `SELECT jsonl_path FROM superseded_provider_sessions
392
+ WHERE session_id = ? AND jsonl_path IS NOT NULL`
393
+ )
394
+ .all(sessionId) as Array<{ jsonl_path: string }>;
395
+
396
+ return rows.map((row) => row.jsonl_path);
397
+ },
398
+
399
+ /**
400
+ * Forgets what a session left behind, once the session itself is gone.
401
+ *
402
+ * Without this the record outlives the row it was written for and keeps the
403
+ * indexer refusing a transcript that no longer belongs to anything — a
404
+ * conversation invisible to the app and impossible to delete through it.
405
+ */
406
+ clearSupersededProviderSessions(sessionId: string): void {
407
+ const db = getConnection();
408
+ db.prepare('DELETE FROM superseded_provider_sessions WHERE session_id = ?').run(sessionId);
409
+ },
410
+
238
411
  /**
239
412
  * Records the model one session runs with.
240
413
  *
@@ -521,4 +694,32 @@ export const sessionsDb = {
521
694
  const db = getConnection();
522
695
  return db.prepare('DELETE FROM sessions WHERE session_id = ?').run(sessionId).changes > 0;
523
696
  },
697
+
698
+ /** Used by the OpenCode synchronizer to remove indexed child sessions by their native id. */
699
+ deleteSessionByProviderSessionId(providerSessionId: string, provider: string): boolean {
700
+ const db = getConnection();
701
+ return db
702
+ .prepare('DELETE FROM sessions WHERE provider_session_id = ? AND provider = ?')
703
+ .run(providerSessionId, provider).changes > 0;
704
+ },
705
+
706
+ /**
707
+ * Lists every indexed session that claims a transcript file on disk.
708
+ *
709
+ * Only rows with a `jsonl_path` are returned, which deliberately excludes
710
+ * app-created sessions still waiting for their first provider write and
711
+ * OpenCode rows (whose transcripts all live inside one shared sqlite file).
712
+ * Used by the session synchronizer to find rows whose transcript has been
713
+ * deleted underneath the index.
714
+ */
715
+ getSessionsWithTranscriptPath(): Array<{ session_id: string; jsonl_path: string }> {
716
+ const db = getConnection();
717
+ return db
718
+ .prepare(
719
+ `SELECT session_id, jsonl_path
720
+ FROM sessions
721
+ WHERE jsonl_path IS NOT NULL AND jsonl_path <> ''`
722
+ )
723
+ .all() as Array<{ session_id: string; jsonl_path: string }>;
724
+ },
524
725
  };
@@ -0,0 +1,80 @@
1
+ import { getConnection } from '@/modules/database/connection.js';
2
+
3
+ type PreferenceRow = {
4
+ preference_key: string;
5
+ preference_value: string;
6
+ };
7
+
8
+ /**
9
+ * Decodes one stored preference, dropping any row whose JSON no longer parses
10
+ * rather than failing the whole read. A single corrupted value must not cost
11
+ * the user every other setting they have.
12
+ */
13
+ function decodePreference(row: PreferenceRow): [string, unknown] | null {
14
+ try {
15
+ return [row.preference_key, JSON.parse(row.preference_value) as unknown];
16
+ } catch {
17
+ console.warn(`[UserPreferences] Dropping unreadable value for "${row.preference_key}"`);
18
+ return null;
19
+ }
20
+ }
21
+
22
+ export const userPreferencesDb = {
23
+ /**
24
+ * Returns every preference the user has ever set, as one object.
25
+ *
26
+ * The client fetches this once on start-up, so absent keys simply mean "the
27
+ * user never changed this" and the client applies its own default.
28
+ */
29
+ getPreferences(userId: number): Record<string, unknown> {
30
+ const db = getConnection();
31
+ const rows = db
32
+ .prepare(
33
+ `SELECT preference_key, preference_value
34
+ FROM user_preferences
35
+ WHERE user_id = ?`
36
+ )
37
+ .all(userId) as PreferenceRow[];
38
+
39
+ const preferences: Record<string, unknown> = {};
40
+ for (const row of rows) {
41
+ const decoded = decodePreference(row);
42
+ if (decoded) {
43
+ preferences[decoded[0]] = decoded[1];
44
+ }
45
+ }
46
+
47
+ return preferences;
48
+ },
49
+
50
+ /**
51
+ * Merge-patches preferences: keys present in `updates` are written, keys
52
+ * absent are left alone, and a key given as `undefined` is deleted.
53
+ *
54
+ * Runs in one transaction so a multi-key save from the settings dialog can
55
+ * never be observed half-applied.
56
+ */
57
+ savePreferences(userId: number, updates: Record<string, unknown>): void {
58
+ const db = getConnection();
59
+ const upsert = db.prepare(
60
+ `INSERT INTO user_preferences (user_id, preference_key, preference_value, updated_at)
61
+ VALUES (?, ?, ?, CURRENT_TIMESTAMP)
62
+ ON CONFLICT(user_id, preference_key) DO UPDATE SET
63
+ preference_value = excluded.preference_value,
64
+ updated_at = CURRENT_TIMESTAMP`
65
+ );
66
+ const remove = db.prepare(
67
+ 'DELETE FROM user_preferences WHERE user_id = ? AND preference_key = ?'
68
+ );
69
+
70
+ db.transaction(() => {
71
+ for (const [key, value] of Object.entries(updates)) {
72
+ if (value === undefined) {
73
+ remove.run(userId, key);
74
+ continue;
75
+ }
76
+ upsert.run(userId, key, JSON.stringify(value));
77
+ }
78
+ })();
79
+ },
80
+ };
@@ -96,6 +96,30 @@ CREATE TABLE IF NOT EXISTS projects (
96
96
  );
97
97
  `;
98
98
 
99
+ export const SCHEDULED_MESSAGES_TABLE_SCHEMA_SQL = `
100
+ CREATE TABLE IF NOT EXISTS scheduled_messages (
101
+ id TEXT PRIMARY KEY,
102
+ user_id INTEGER NOT NULL,
103
+ session_id TEXT NOT NULL,
104
+ content TEXT NOT NULL,
105
+ -- Composer preferences (model, effort, permission mode, attachments) as
106
+ -- they were when the message was scheduled, so it runs the way the user
107
+ -- set it up rather than however the session is configured hours later.
108
+ options TEXT NOT NULL DEFAULT '{}',
109
+ -- UTC. The client sends an absolute instant so the schedule does not move
110
+ -- when the user changes time zone between scheduling and firing.
111
+ scheduled_for DATETIME NOT NULL,
112
+ -- pending | sent | failed | cancelled
113
+ status TEXT NOT NULL DEFAULT 'pending',
114
+ -- Why a failed one failed, shown next to it in the composer.
115
+ failure_reason TEXT,
116
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
117
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
118
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
119
+ FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
120
+ );
121
+ `;
122
+
99
123
  export const SESSIONS_TABLE_SCHEMA_SQL = `
100
124
  CREATE TABLE IF NOT EXISTS sessions (
101
125
  session_id TEXT NOT NULL,
@@ -114,6 +138,10 @@ CREATE TABLE IF NOT EXISTS sessions (
114
138
  -- restores its exact runtime configuration instead of provider defaults.
115
139
  model TEXT,
116
140
  effort TEXT,
141
+ -- The app session this one was branched from, NULL for sessions created
142
+ -- normally. Informational only: a fork is a fully independent provider
143
+ -- session, and deleting the source does not affect it.
144
+ forked_from_session_id TEXT,
117
145
  isArchived BOOLEAN DEFAULT 0,
118
146
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
119
147
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
@@ -146,6 +174,11 @@ CREATE TABLE IF NOT EXISTS app_config (
146
174
  * controlled in each provider's `-models.provider.ts` adapter so they can be
147
175
  * updated without migrating application data. `model_id` is unique only within
148
176
  * a provider because different CLIs can accept the same identifier.
177
+ *
178
+ * `effort_values` (JSON array of level ids) and `effort_default` hold the
179
+ * reasoning-effort levels the user declared for the model; both stay NULL when
180
+ * none were declared. They sit after the timestamps so fresh installs match the
181
+ * column order that `runMigrations` produces on upgraded databases.
149
182
  */
150
183
  export const PROVIDER_MODELS_TABLE_SCHEMA_SQL = `
151
184
  CREATE TABLE IF NOT EXISTS provider_models (
@@ -156,10 +189,74 @@ CREATE TABLE IF NOT EXISTS provider_models (
156
189
  sort_order INTEGER NOT NULL DEFAULT 0,
157
190
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
158
191
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
192
+ effort_values TEXT DEFAULT NULL,
193
+ effort_default TEXT DEFAULT NULL,
159
194
  UNIQUE(provider, model_id)
160
195
  );
161
196
  `;
162
197
 
198
+ /**
199
+ * Per-user application preferences that used to live in browser localStorage.
200
+ *
201
+ * One row per (user, key); `preference_value` is always a JSON document so a
202
+ * key can hold a scalar (`"dark"`), a flag (`false`) or a whole settings blob
203
+ * without the schema having to know which. Keeping them server-side is what
204
+ * makes a preference follow the user from one device to another.
205
+ */
206
+ export const USER_PREFERENCES_TABLE_SCHEMA_SQL = `
207
+ CREATE TABLE IF NOT EXISTS user_preferences (
208
+ user_id INTEGER NOT NULL,
209
+ preference_key TEXT NOT NULL,
210
+ preference_value TEXT NOT NULL,
211
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
212
+ PRIMARY KEY (user_id, preference_key),
213
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
214
+ );
215
+ `;
216
+
217
+ /**
218
+ * Unsent composer text and queued messages, per user and per chat scope.
219
+ *
220
+ * `draft_scope` is a session id, or `project:<projectId>` for a chat that has
221
+ * not been sent yet and therefore has no session. Storing this server-side is
222
+ * what lets a message typed on a laptop be finished on a phone.
223
+ */
224
+ export const SESSION_DRAFTS_TABLE_SCHEMA_SQL = `
225
+ CREATE TABLE IF NOT EXISTS session_drafts (
226
+ user_id INTEGER NOT NULL,
227
+ draft_scope TEXT NOT NULL,
228
+ draft_text TEXT NOT NULL DEFAULT '',
229
+ queued_message TEXT,
230
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
231
+ PRIMARY KEY (user_id, draft_scope),
232
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
233
+ );
234
+ `;
235
+
236
+ /**
237
+ * Provider sessions an app session has moved off, and must never move back to.
238
+ *
239
+ * Editing a message on a provider that cannot resume a transcript partway
240
+ * (Codex) is done by branching: the conversation is copied up to the edited
241
+ * turn and the app session follows the copy. The original transcript is left
242
+ * on disk untouched — nothing is deleted — but it is no longer the session's,
243
+ * and the indexer would otherwise rediscover it on its next full scan and hand
244
+ * the session back to the version the user edited away from.
245
+ */
246
+ export const SUPERSEDED_PROVIDER_SESSIONS_TABLE_SCHEMA_SQL = `
247
+ CREATE TABLE IF NOT EXISTS superseded_provider_sessions (
248
+ provider_session_id TEXT NOT NULL,
249
+ provider TEXT NOT NULL,
250
+ session_id TEXT NOT NULL,
251
+ -- The transcript the session left behind. Recorded because the session row
252
+ -- stops pointing at it, and "delete this conversation from disk" has to
253
+ -- reach every file the conversation ever lived in.
254
+ jsonl_path TEXT,
255
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
256
+ PRIMARY KEY (provider_session_id, provider)
257
+ );
258
+ `;
259
+
163
260
  export const INIT_SCHEMA_SQL = `
164
261
  -- Initialize authentication database
165
262
  PRAGMA foreign_keys = ON;
@@ -207,4 +304,10 @@ ${APP_CONFIG_TABLE_SCHEMA_SQL}
207
304
  ${PROVIDER_MODELS_TABLE_SCHEMA_SQL}
208
305
  CREATE INDEX IF NOT EXISTS idx_provider_models_provider_order
209
306
  ON provider_models(provider, sort_order, id);
307
+
308
+ ${USER_PREFERENCES_TABLE_SCHEMA_SQL}
309
+
310
+ ${SESSION_DRAFTS_TABLE_SCHEMA_SQL}
311
+
312
+ ${SUPERSEDED_PROVIDER_SESSIONS_TABLE_SCHEMA_SQL}
210
313
  `;
@@ -4,6 +4,8 @@ import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import test from 'node:test';
6
6
 
7
+ import Database from 'better-sqlite3';
8
+
7
9
  import {
8
10
  closeConnection,
9
11
  getConnection,
@@ -35,6 +37,8 @@ test('provider model repository stores custom rows only and maintains session re
35
37
  'sort_order',
36
38
  'created_at',
37
39
  'updated_at',
40
+ 'effort_values',
41
+ 'effort_default',
38
42
  ]);
39
43
  assert.deepEqual(providerModelsDb.listCustomProviderModels('codex'), []);
40
44
 
@@ -43,6 +47,7 @@ test('provider model repository stores custom rows only and maintains session re
43
47
  id: 'gateway/model-v1',
44
48
  });
45
49
  assert.equal(custom.modelId, 'gateway/model-v1');
50
+ assert.equal(custom.effort, null);
46
51
  assert.equal(
47
52
  providerModelsDb.findCustomProviderModelByModelId('codex', 'gateway/model-v1')?.recordId,
48
53
  custom.recordId,
@@ -123,3 +128,111 @@ test('migrations create the provider model index on an install that lacks it', a
123
128
  await rm(tempDirectory, { recursive: true, force: true });
124
129
  }
125
130
  });
131
+
132
+ test('provider model repository persists declared effort levels', async () => {
133
+ const previousDatabasePath = process.env.DATABASE_PATH;
134
+ const tempDirectory = await mkdtemp(path.join(os.tmpdir(), 'provider-model-effort-'));
135
+ const databasePath = path.join(tempDirectory, 'auth.db');
136
+
137
+ closeConnection();
138
+ process.env.DATABASE_PATH = databasePath;
139
+ await writeFile(databasePath, '');
140
+ await initializeDatabase();
141
+
142
+ try {
143
+ const custom = providerModelsDb.createCustomProviderModel('claude', {
144
+ model: 'My Custom Model',
145
+ id: 'my-custom-model',
146
+ effort: { values: ['low', 'high'], default: 'high' },
147
+ });
148
+ assert.deepEqual(custom.effort, { values: ['low', 'high'], default: 'high' });
149
+ assert.deepEqual(
150
+ providerModelsDb.listCustomProviderModels('claude')[0]?.effort,
151
+ { values: ['low', 'high'], default: 'high' },
152
+ );
153
+
154
+ // Omitting effort on update keeps the stored levels.
155
+ const renamed = providerModelsDb.updateCustomProviderModel('claude', custom.recordId, {
156
+ model: 'Renamed',
157
+ id: 'my-custom-model',
158
+ });
159
+ assert.deepEqual(renamed?.effort, { values: ['low', 'high'], default: 'high' });
160
+
161
+ const narrowed = providerModelsDb.updateCustomProviderModel('claude', custom.recordId, {
162
+ model: 'Renamed',
163
+ id: 'my-custom-model',
164
+ effort: { values: ['medium'] },
165
+ });
166
+ assert.deepEqual(narrowed?.effort, { values: ['medium'] });
167
+
168
+ const cleared = providerModelsDb.updateCustomProviderModel('claude', custom.recordId, {
169
+ model: 'Renamed',
170
+ id: 'my-custom-model',
171
+ effort: null,
172
+ });
173
+ assert.equal(cleared?.effort, null);
174
+ const row = getConnection()
175
+ .prepare('SELECT effort_values, effort_default FROM provider_models WHERE id = ?')
176
+ .get(custom.recordId);
177
+ assert.deepEqual(row, { effort_values: null, effort_default: null });
178
+ } finally {
179
+ closeConnection();
180
+ if (previousDatabasePath === undefined) {
181
+ delete process.env.DATABASE_PATH;
182
+ } else {
183
+ process.env.DATABASE_PATH = previousDatabasePath;
184
+ }
185
+ await rm(tempDirectory, { recursive: true, force: true });
186
+ }
187
+ });
188
+
189
+ test('migrations add effort columns to an existing provider model table and keep old rows NULL', async () => {
190
+ const previousDatabasePath = process.env.DATABASE_PATH;
191
+ const tempDirectory = await mkdtemp(path.join(os.tmpdir(), 'provider-model-effort-migration-'));
192
+ const databasePath = path.join(tempDirectory, 'auth.db');
193
+
194
+ // The table as it shipped before effort metadata existed, with one user row.
195
+ const legacyDb = new Database(databasePath);
196
+ legacyDb.exec(`
197
+ CREATE TABLE provider_models (
198
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
199
+ provider TEXT NOT NULL CHECK (provider IN ('claude', 'cursor', 'codex', 'opencode')),
200
+ model_id TEXT NOT NULL,
201
+ model_name TEXT NOT NULL,
202
+ sort_order INTEGER NOT NULL DEFAULT 0,
203
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
204
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
205
+ UNIQUE(provider, model_id)
206
+ );
207
+ INSERT INTO provider_models (provider, model_id, model_name, sort_order)
208
+ VALUES ('codex', 'legacy-model', 'Legacy Model', 0);
209
+ `);
210
+ legacyDb.close();
211
+
212
+ closeConnection();
213
+ process.env.DATABASE_PATH = databasePath;
214
+ await initializeDatabase();
215
+
216
+ try {
217
+ const columnNames = (getConnection().prepare('PRAGMA table_info(provider_models)').all() as Array<{
218
+ name: string;
219
+ }>).map((column) => column.name);
220
+ assert.deepEqual(columnNames.slice(-2), ['effort_values', 'effort_default']);
221
+
222
+ const [legacy] = providerModelsDb.listCustomProviderModels('codex');
223
+ assert.equal(legacy?.modelId, 'legacy-model');
224
+ assert.equal(legacy?.effort, null);
225
+
226
+ // Running the migrations again is a no-op.
227
+ runMigrations(getConnection());
228
+ assert.equal(providerModelsDb.listCustomProviderModels('codex')[0]?.effort, null);
229
+ } finally {
230
+ closeConnection();
231
+ if (previousDatabasePath === undefined) {
232
+ delete process.env.DATABASE_PATH;
233
+ } else {
234
+ process.env.DATABASE_PATH = previousDatabasePath;
235
+ }
236
+ await rm(tempDirectory, { recursive: true, force: true });
237
+ }
238
+ });
@@ -72,6 +72,58 @@ test('createSession reactivates archived rows when the session becomes active ag
72
72
  });
73
73
  });
74
74
 
75
+ test("createSession leaves an archived row archived when the transcript has not changed", async () => {
76
+ await withIsolatedDatabase(() => {
77
+ const createdAt = "2026-07-18T09:00:00.000Z";
78
+ const updatedAt = "2026-07-18T10:00:00.000Z";
79
+ const jsonlPath = "/transcripts/session-untouched.jsonl";
80
+
81
+ sessionsDb.createSession("session-untouched", "claude", "/workspace/demo-project", "A Name", createdAt, updatedAt, jsonlPath);
82
+ sessionsDb.updateSessionIsArchived("session-untouched", true);
83
+
84
+ // A full rescan re-indexes every transcript created since the last scan,
85
+ // changed or not, and hands over the timestamps the file still carries.
86
+ sessionsDb.createSession("session-untouched", "claude", "/workspace/demo-project", "A Name", createdAt, updatedAt, jsonlPath);
87
+
88
+ assert.equal(sessionsDb.getSessionById("session-untouched")?.isArchived, 1);
89
+ assert.equal(sessionsDb.getArchivedSessions().length, 1);
90
+ assert.equal(sessionsDb.getAllSessions().length, 0);
91
+
92
+ // Actually writing to the session again still brings it back.
93
+ sessionsDb.createSession("session-untouched", "claude", "/workspace/demo-project", "A Name", createdAt, "2026-07-18T11:00:00.000Z", jsonlPath);
94
+
95
+ assert.equal(sessionsDb.getSessionById("session-untouched")?.isArchived, 0);
96
+ });
97
+ });
98
+
99
+ test("the upsert path counts an omitted timestamp as activity", async () => {
100
+ await withIsolatedDatabase(() => {
101
+ // An app-created row carries no provider id, so indexing it takes the
102
+ // INSERT ... ON CONFLICT branch rather than the UPDATE above. Its
103
+ // updated_at is CURRENT_TIMESTAMP, which resolves to whole seconds, so a
104
+ // call in the same second is not *newer* -- the omitted timestamp itself
105
+ // has to be what reactivates the row.
106
+ sessionsDb.createAppSession("session-legacy", "claude", "/workspace/demo-project");
107
+ sessionsDb.updateSessionIsArchived("session-legacy", true);
108
+
109
+ sessionsDb.createSession("session-legacy", "claude", "/workspace/demo-project", "Indexed Name");
110
+
111
+ assert.equal(sessionsDb.getSessionById("session-legacy")?.isArchived, 0);
112
+ });
113
+ });
114
+
115
+ test("the upsert path leaves an archived row alone for a transcript older than it", async () => {
116
+ await withIsolatedDatabase(() => {
117
+ sessionsDb.createAppSession("session-stale", "claude", "/workspace/demo-project");
118
+ sessionsDb.updateSessionIsArchived("session-stale", true);
119
+
120
+ sessionsDb.createSession("session-stale", "claude", "/workspace/demo-project", "Indexed Name", "2026-07-18T09:00:00.000Z", "2026-07-18T10:00:00.000Z", "/transcripts/session-stale.jsonl");
121
+
122
+ assert.equal(sessionsDb.getSessionById("session-stale")?.isArchived, 1);
123
+ });
124
+ });
125
+
126
+
75
127
  test('repository reads normalize SQLite UTC timestamps to ISO strings', async () => {
76
128
  await withIsolatedDatabase(() => {
77
129
  sessionsDb.createAppSession('session-timezone', 'claude', '/workspace/demo-project');