circuschief 0.1.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 (319) hide show
  1. package/package.json +33 -0
  2. package/packages/server/bin/cli.js +4 -0
  3. package/packages/server/src/agents/AgentGateway.js +64 -0
  4. package/packages/server/src/agents/BaseAgent.js +41 -0
  5. package/packages/server/src/agents/LoggingAgentWrapper.js +73 -0
  6. package/packages/server/src/agents/adapters/ClaudeCodeAdapter.js +33 -0
  7. package/packages/server/src/agents/adapters/CodexAdapter.js +26 -0
  8. package/packages/server/src/agents/types.js +43 -0
  9. package/packages/server/src/agents/vcr/CassetteStore.js +111 -0
  10. package/packages/server/src/agents/vcr/VCRAgentAdapter.js +126 -0
  11. package/packages/server/src/agents/vcr/VCRSummaryWrapper.js +71 -0
  12. package/packages/server/src/api/canvas-helpers.js +249 -0
  13. package/packages/server/src/api/canvas-trash-routes.js +205 -0
  14. package/packages/server/src/api/canvas.js +331 -0
  15. package/packages/server/src/api/commandButtons.js +312 -0
  16. package/packages/server/src/api/commands.js +169 -0
  17. package/packages/server/src/api/filesystem.js +62 -0
  18. package/packages/server/src/api/git.js +85 -0
  19. package/packages/server/src/api/index.js +44 -0
  20. package/packages/server/src/api/kanban.js +342 -0
  21. package/packages/server/src/api/metrics.js +194 -0
  22. package/packages/server/src/api/projects-helpers.js +43 -0
  23. package/packages/server/src/api/projects-session-helpers.js +295 -0
  24. package/packages/server/src/api/projects.js +384 -0
  25. package/packages/server/src/api/providers.js +249 -0
  26. package/packages/server/src/api/quickResponses.js +129 -0
  27. package/packages/server/src/api/sessions-archive.js +69 -0
  28. package/packages/server/src/api/sessions-commands.js +220 -0
  29. package/packages/server/src/api/sessions-conversations.js +168 -0
  30. package/packages/server/src/api/sessions-draft.js +72 -0
  31. package/packages/server/src/api/sessions-lifecycle.js +190 -0
  32. package/packages/server/src/api/sessions-messages.js +141 -0
  33. package/packages/server/src/api/sessions-notes.js +51 -0
  34. package/packages/server/src/api/sessions-patch.js +252 -0
  35. package/packages/server/src/api/sessions-streaming.js +86 -0
  36. package/packages/server/src/api/sessions.js +269 -0
  37. package/packages/server/src/api/settings.js +194 -0
  38. package/packages/server/src/api/templates.js +63 -0
  39. package/packages/server/src/app.js +51 -0
  40. package/packages/server/src/database.js +58 -0
  41. package/packages/server/src/db/AgentCallLogRepository.js +322 -0
  42. package/packages/server/src/db/AttachmentRepository.js +191 -0
  43. package/packages/server/src/db/BaseRepository.js +39 -0
  44. package/packages/server/src/db/CanvasItemRepository.js +315 -0
  45. package/packages/server/src/db/CommandButtonRepository.js +75 -0
  46. package/packages/server/src/db/CommandRunRepository.js +219 -0
  47. package/packages/server/src/db/ConversationRepository.js +379 -0
  48. package/packages/server/src/db/DatabaseManager.js +91 -0
  49. package/packages/server/src/db/KanbanBoardRepository.js +92 -0
  50. package/packages/server/src/db/KanbanCardRepository.js +286 -0
  51. package/packages/server/src/db/KanbanLaneRepository.js +279 -0
  52. package/packages/server/src/db/MessageRepository.js +156 -0
  53. package/packages/server/src/db/ProjectDefaultsRepository.js +173 -0
  54. package/packages/server/src/db/ProjectRepository.js +110 -0
  55. package/packages/server/src/db/ProviderRepository.js +307 -0
  56. package/packages/server/src/db/QuickResponseRepository.js +186 -0
  57. package/packages/server/src/db/SessionNoteRepository.js +60 -0
  58. package/packages/server/src/db/SessionRepository.js +314 -0
  59. package/packages/server/src/db/SessionSummaryRepository.js +200 -0
  60. package/packages/server/src/db/SessionTemplateRepository.js +171 -0
  61. package/packages/server/src/db/SettingsRepository.js +211 -0
  62. package/packages/server/src/db/TodoRepository.js +132 -0
  63. package/packages/server/src/db/WorkLogRepository.js +122 -0
  64. package/packages/server/src/db/conversation-helpers.js +119 -0
  65. package/packages/server/src/db/index.js +100 -0
  66. package/packages/server/src/db/migrations/canvasItemsMigrations.js +109 -0
  67. package/packages/server/src/db/migrations/conversationsMigrations.js +183 -0
  68. package/packages/server/src/db/migrations/index.js +199 -0
  69. package/packages/server/src/db/migrations/kanbanMigrations.js +99 -0
  70. package/packages/server/src/db/migrations/migrationUtils.js +55 -0
  71. package/packages/server/src/db/migrations/miscMigrations.js +242 -0
  72. package/packages/server/src/db/migrations/projectsMigrations.js +95 -0
  73. package/packages/server/src/db/migrations/sessionsMigrations.js +282 -0
  74. package/packages/server/src/db/session-helpers.js +150 -0
  75. package/packages/server/src/index.js +106 -0
  76. package/packages/server/src/logger.js +22 -0
  77. package/packages/server/src/middleware/sessionLookup.js +57 -0
  78. package/packages/server/src/middleware/upload.js +94 -0
  79. package/packages/server/src/schema.sql +363 -0
  80. package/packages/server/src/services/agentCallLogger.js +116 -0
  81. package/packages/server/src/services/canvasStore.js +56 -0
  82. package/packages/server/src/services/childSessionContext.js +61 -0
  83. package/packages/server/src/services/commandRunner.js +422 -0
  84. package/packages/server/src/services/conversationContext.js +72 -0
  85. package/packages/server/src/services/diffService.js +172 -0
  86. package/packages/server/src/services/draftSessionService.js +181 -0
  87. package/packages/server/src/services/encryption.js +134 -0
  88. package/packages/server/src/services/ghService.js +169 -0
  89. package/packages/server/src/services/gitService.js +520 -0
  90. package/packages/server/src/services/gitSessionSetup.js +48 -0
  91. package/packages/server/src/services/hookService.js +60 -0
  92. package/packages/server/src/services/kanbanService.js +262 -0
  93. package/packages/server/src/services/kanbanTriggers.js +273 -0
  94. package/packages/server/src/services/nodeSpawnHelper.js +63 -0
  95. package/packages/server/src/services/prStatusService.js +204 -0
  96. package/packages/server/src/services/prUrlService.js +224 -0
  97. package/packages/server/src/services/providerTestService.js +81 -0
  98. package/packages/server/src/services/scheduleService.js +110 -0
  99. package/packages/server/src/services/schedulerService.js +281 -0
  100. package/packages/server/src/services/sessionDuplicator.js +63 -0
  101. package/packages/server/src/services/sessionErrors.js +173 -0
  102. package/packages/server/src/services/sessionExecution.js +378 -0
  103. package/packages/server/src/services/sessionManager.js +356 -0
  104. package/packages/server/src/services/sessionPrompts.js +427 -0
  105. package/packages/server/src/services/sessionProvider.js +107 -0
  106. package/packages/server/src/services/slashCommandDiscovery.js +258 -0
  107. package/packages/server/src/services/slashCommandPluginDiscovery.js +216 -0
  108. package/packages/server/src/services/slashCommandService.js +306 -0
  109. package/packages/server/src/services/streamEventCallbacks.js +170 -0
  110. package/packages/server/src/services/streamEventHandler.js +488 -0
  111. package/packages/server/src/services/streamUsageHandler.js +228 -0
  112. package/packages/server/src/services/summaryBroadcast.js +61 -0
  113. package/packages/server/src/services/summaryClaudeClient.js +180 -0
  114. package/packages/server/src/services/summaryPrompts.js +169 -0
  115. package/packages/server/src/services/summaryService.js +552 -0
  116. package/packages/server/src/services/summaryStaleCheck.js +35 -0
  117. package/packages/server/src/services/systemMonitor.js +281 -0
  118. package/packages/server/src/services/templateTriggerService.js +197 -0
  119. package/packages/server/src/services/terminalOutput.js +160 -0
  120. package/packages/server/src/services/todoStore.js +58 -0
  121. package/packages/server/src/services/usageTracker.js +69 -0
  122. package/packages/server/src/services/withConcurrencyGuard.js +110 -0
  123. package/packages/server/src/websocket.js +10 -0
  124. package/packages/server/src/ws/WebSocketManager.js +240 -0
  125. package/packages/server/src/ws/index.js +50 -0
  126. package/packages/shared/package.json +27 -0
  127. package/packages/shared/src/constants.js +44 -0
  128. package/packages/shared/src/contracts/canvas.js +25 -0
  129. package/packages/shared/src/contracts/commandButtons.js +36 -0
  130. package/packages/shared/src/contracts/kanban.js +142 -0
  131. package/packages/shared/src/contracts/projects.js +63 -0
  132. package/packages/shared/src/contracts/providers.js +81 -0
  133. package/packages/shared/src/contracts/quickResponses.js +44 -0
  134. package/packages/shared/src/contracts/sessions.js +112 -0
  135. package/packages/shared/src/contracts/templates.js +51 -0
  136. package/packages/shared/src/index.js +5 -0
  137. package/packages/shared/src/protocol.js +76 -0
  138. package/packages/shared/src/routeParams.js +36 -0
  139. package/packages/shared/src/types.js +167 -0
  140. package/packages/shared/src/utils.js +101 -0
  141. package/packages/web/dist/assets/ActiveSessionsView-BQc76Jc8.js +1 -0
  142. package/packages/web/dist/assets/ActiveSessionsView-ofSvx-K1.css +1 -0
  143. package/packages/web/dist/assets/AgentLogsView-CTCjHjsu.js +2 -0
  144. package/packages/web/dist/assets/AgentLogsView-D90PnQVk.css +1 -0
  145. package/packages/web/dist/assets/ApiClient-Dbs1H78V.js +1 -0
  146. package/packages/web/dist/assets/ArchiveConfirmModal-CCxSZ52u.js +1 -0
  147. package/packages/web/dist/assets/ArchiveConfirmModal-CQZeuYBz.css +1 -0
  148. package/packages/web/dist/assets/CommandButtonDetailView-CF_-LXpU.js +1 -0
  149. package/packages/web/dist/assets/CommandButtonDetailView-DBm3rzhw.css +1 -0
  150. package/packages/web/dist/assets/EffortLevelSelector-BQaQmU2d.css +1 -0
  151. package/packages/web/dist/assets/EffortLevelSelector-DPofLvm-.js +1 -0
  152. package/packages/web/dist/assets/GeneralSettingsView-BCf53fpC.css +1 -0
  153. package/packages/web/dist/assets/GeneralSettingsView-BY1G-Kv8.js +1 -0
  154. package/packages/web/dist/assets/InterpolationHelp-CgdbNcJB.js +1 -0
  155. package/packages/web/dist/assets/InterpolationHelp-iNxTxmhs.css +1 -0
  156. package/packages/web/dist/assets/MarkdownEditor-CqT1U8lo.js +2 -0
  157. package/packages/web/dist/assets/MarkdownEditor-enuH2yvP.css +1 -0
  158. package/packages/web/dist/assets/ModelSelector-BBn_Ve0D.js +1 -0
  159. package/packages/web/dist/assets/ModelSelector-DPPD-92R.css +1 -0
  160. package/packages/web/dist/assets/NewSessionView-Bo5l49nu.js +3 -0
  161. package/packages/web/dist/assets/NewSessionView-Byoi1XdQ.css +1 -0
  162. package/packages/web/dist/assets/PathChooser-BoMGzeg2.css +1 -0
  163. package/packages/web/dist/assets/PathChooser-Cx9gQ-Qt.js +1 -0
  164. package/packages/web/dist/assets/ProjectEditView-BFuscj-V.js +1 -0
  165. package/packages/web/dist/assets/ProjectEditView-DNwBUNRk.css +1 -0
  166. package/packages/web/dist/assets/ProjectListView-C55H1JHQ.css +1 -0
  167. package/packages/web/dist/assets/ProjectListView-Dj0jBZ46.js +1 -0
  168. package/packages/web/dist/assets/ProjectNewView-Brdp-xUu.js +1 -0
  169. package/packages/web/dist/assets/ProjectNewView-CpgE4R-l.css +1 -0
  170. package/packages/web/dist/assets/ProvidersView-B_QQF3RM.css +1 -0
  171. package/packages/web/dist/assets/ProvidersView-Cxc-1skq.js +1 -0
  172. package/packages/web/dist/assets/QuickResponseSettings-B2eVAtHW.js +1 -0
  173. package/packages/web/dist/assets/QuickResponseSettings-B8188A1D.css +1 -0
  174. package/packages/web/dist/assets/QuickResponsesPanel-DIBQFj0W.css +1 -0
  175. package/packages/web/dist/assets/QuickResponsesPanel-lU8pW2B0.js +1 -0
  176. package/packages/web/dist/assets/ResizableTextarea-B5nAA0RV.css +1 -0
  177. package/packages/web/dist/assets/ResizableTextarea-DSy1mWGY.js +1 -0
  178. package/packages/web/dist/assets/SessionCard-BvjLwVYg.js +1 -0
  179. package/packages/web/dist/assets/SessionCard-D20G3bX8.css +1 -0
  180. package/packages/web/dist/assets/SessionDetailView-BQbPg-RJ.js +36 -0
  181. package/packages/web/dist/assets/SessionDetailView-BrMG4p2-.css +1 -0
  182. package/packages/web/dist/assets/SessionFormOptions-BgqFR-5f.js +1 -0
  183. package/packages/web/dist/assets/SessionFormOptions-BuLlDF-7.css +1 -0
  184. package/packages/web/dist/assets/SessionListView-BAIBtJF7.css +1 -0
  185. package/packages/web/dist/assets/SessionListView-CYIHI8qF.js +1 -0
  186. package/packages/web/dist/assets/SessionLogStream-B-FwUMJQ.js +18 -0
  187. package/packages/web/dist/assets/SessionLogStream-zPUTiGbe.css +1 -0
  188. package/packages/web/dist/assets/SettingsView-DC8-hTQ-.css +1 -0
  189. package/packages/web/dist/assets/SettingsView-fZxpiGp7.js +1 -0
  190. package/packages/web/dist/assets/SlashCommandWizard-BB30cSvo.css +1 -0
  191. package/packages/web/dist/assets/SlashCommandWizard-BgaOw9W3.js +1 -0
  192. package/packages/web/dist/assets/SummarySettingsView-DcsmSVJI.css +1 -0
  193. package/packages/web/dist/assets/SummarySettingsView-eeu1Xq86.js +1 -0
  194. package/packages/web/dist/assets/TemplateDetailView-DEPKSwDo.js +1 -0
  195. package/packages/web/dist/assets/TemplateDetailView-DT2m06W7.css +1 -0
  196. package/packages/web/dist/assets/apl-B4CMkyY2.js +1 -0
  197. package/packages/web/dist/assets/asciiarmor-Df11BRmG.js +1 -0
  198. package/packages/web/dist/assets/asn1-EdZsLKOL.js +1 -0
  199. package/packages/web/dist/assets/asterisk-B-8jnY81.js +1 -0
  200. package/packages/web/dist/assets/brainfuck-C4LP7Hcl.js +1 -0
  201. package/packages/web/dist/assets/clike-B9uivgTg.js +1 -0
  202. package/packages/web/dist/assets/clojure-BMjYHr_A.js +1 -0
  203. package/packages/web/dist/assets/cmake-BQqOBYOt.js +1 -0
  204. package/packages/web/dist/assets/cobol-CWcv1MsR.js +1 -0
  205. package/packages/web/dist/assets/coffeescript-S37ZYGWr.js +1 -0
  206. package/packages/web/dist/assets/commandButtons-DNSHH8IA.js +4 -0
  207. package/packages/web/dist/assets/commonlisp-DBKNyK5s.js +1 -0
  208. package/packages/web/dist/assets/crystal-SjHAIU92.js +1 -0
  209. package/packages/web/dist/assets/css-BnMrqG3P.js +1 -0
  210. package/packages/web/dist/assets/cypher-C_CwsFkJ.js +1 -0
  211. package/packages/web/dist/assets/d-pRatUO7H.js +1 -0
  212. package/packages/web/dist/assets/diff-DbItnlRl.js +1 -0
  213. package/packages/web/dist/assets/dockerfile-BKs6k2Af.js +1 -0
  214. package/packages/web/dist/assets/dtd-DF_7sFjM.js +1 -0
  215. package/packages/web/dist/assets/dylan-DwRh75JA.js +1 -0
  216. package/packages/web/dist/assets/ebnf-CDyGwa7X.js +1 -0
  217. package/packages/web/dist/assets/ecl-Cabwm37j.js +1 -0
  218. package/packages/web/dist/assets/eiffel-CnydiIhH.js +1 -0
  219. package/packages/web/dist/assets/elm-vLlmbW-K.js +1 -0
  220. package/packages/web/dist/assets/erlang-BNw1qcRV.js +1 -0
  221. package/packages/web/dist/assets/factor-kuTfRLto.js +1 -0
  222. package/packages/web/dist/assets/fcl-Kvtd6kyn.js +1 -0
  223. package/packages/web/dist/assets/forth-Ffai-XNe.js +1 -0
  224. package/packages/web/dist/assets/fortran-DYz_wnZ1.js +1 -0
  225. package/packages/web/dist/assets/gas-Bneqetm1.js +1 -0
  226. package/packages/web/dist/assets/gherkin-heZmZLOM.js +1 -0
  227. package/packages/web/dist/assets/groovy-D9Dt4D0W.js +1 -0
  228. package/packages/web/dist/assets/haskell-BWDZoCOh.js +1 -0
  229. package/packages/web/dist/assets/haxe-H-WmDvRZ.js +1 -0
  230. package/packages/web/dist/assets/http-DBlCnlav.js +1 -0
  231. package/packages/web/dist/assets/idl-BEugSyMb.js +1 -0
  232. package/packages/web/dist/assets/index-BZlHgDSz.js +1 -0
  233. package/packages/web/dist/assets/index-BhWX8AfE.js +2 -0
  234. package/packages/web/dist/assets/index-Bi3XvF_f.js +1 -0
  235. package/packages/web/dist/assets/index-BqXoPf_D.js +1 -0
  236. package/packages/web/dist/assets/index-CAuTOZSD.js +1 -0
  237. package/packages/web/dist/assets/index-CKYk-fkb.js +1 -0
  238. package/packages/web/dist/assets/index-CTumW_tV.js +318 -0
  239. package/packages/web/dist/assets/index-CVOJVSsC.js +82 -0
  240. package/packages/web/dist/assets/index-CXK2Z3_z.js +1 -0
  241. package/packages/web/dist/assets/index-CYllQ3Vd.js +1 -0
  242. package/packages/web/dist/assets/index-CpsfI08O.js +1 -0
  243. package/packages/web/dist/assets/index-DQkhDeTA.js +3 -0
  244. package/packages/web/dist/assets/index-DWP8iCBp.js +1 -0
  245. package/packages/web/dist/assets/index-DkVb9W_J.js +1 -0
  246. package/packages/web/dist/assets/index-DmKHPbIa.js +1 -0
  247. package/packages/web/dist/assets/index-DrlQi03X.js +1 -0
  248. package/packages/web/dist/assets/index-gmCCsCQ1.css +1 -0
  249. package/packages/web/dist/assets/index-prTEzzgO.js +1 -0
  250. package/packages/web/dist/assets/index-wqgejMCM.js +1 -0
  251. package/packages/web/dist/assets/index-yh0ZHIWw.js +7 -0
  252. package/packages/web/dist/assets/javascript-qCveANmP.js +1 -0
  253. package/packages/web/dist/assets/julia-DuME0IfC.js +1 -0
  254. package/packages/web/dist/assets/livescript-BwQOo05w.js +1 -0
  255. package/packages/web/dist/assets/lua-BgMRiT3U.js +1 -0
  256. package/packages/web/dist/assets/mathematica-DTrFuWx2.js +1 -0
  257. package/packages/web/dist/assets/mbox-CNhZ1qSd.js +1 -0
  258. package/packages/web/dist/assets/mirc-CjQqDB4T.js +1 -0
  259. package/packages/web/dist/assets/mllike-CXdrOF99.js +1 -0
  260. package/packages/web/dist/assets/modelica-Dc1JOy9r.js +1 -0
  261. package/packages/web/dist/assets/mscgen-BA5vi2Kp.js +1 -0
  262. package/packages/web/dist/assets/mumps-BT43cFF4.js +1 -0
  263. package/packages/web/dist/assets/nginx-DdIZxoE0.js +1 -0
  264. package/packages/web/dist/assets/nsis-LdVXkNf5.js +1 -0
  265. package/packages/web/dist/assets/ntriples-BfvgReVJ.js +1 -0
  266. package/packages/web/dist/assets/octave-Ck1zUtKM.js +1 -0
  267. package/packages/web/dist/assets/oz-BzwKVEFT.js +1 -0
  268. package/packages/web/dist/assets/pascal--L3eBynH.js +1 -0
  269. package/packages/web/dist/assets/perl-CdXCOZ3F.js +1 -0
  270. package/packages/web/dist/assets/pig-CevX1Tat.js +1 -0
  271. package/packages/web/dist/assets/powershell-CFHJl5sT.js +1 -0
  272. package/packages/web/dist/assets/projects-DbBQQH-V.js +1 -0
  273. package/packages/web/dist/assets/properties-C78fOPTZ.js +1 -0
  274. package/packages/web/dist/assets/protobuf-ChK-085T.js +1 -0
  275. package/packages/web/dist/assets/providers-ceCc4xRU.js +1 -0
  276. package/packages/web/dist/assets/pug-DukmZTjD.js +1 -0
  277. package/packages/web/dist/assets/puppet-DMA9R1ak.js +1 -0
  278. package/packages/web/dist/assets/python-BuPzkPfP.js +1 -0
  279. package/packages/web/dist/assets/q-pXgVlZs6.js +1 -0
  280. package/packages/web/dist/assets/r-DUYO_cvP.js +1 -0
  281. package/packages/web/dist/assets/rpm-CTu-6PCP.js +1 -0
  282. package/packages/web/dist/assets/ruby-B2Rjki9n.js +1 -0
  283. package/packages/web/dist/assets/sas-B4kiWyti.js +1 -0
  284. package/packages/web/dist/assets/scheme-C41bIUwD.js +1 -0
  285. package/packages/web/dist/assets/sessions-D681M81k.js +1 -0
  286. package/packages/web/dist/assets/settings-D0evez2V.js +1 -0
  287. package/packages/web/dist/assets/shell-CjFT_Tl9.js +1 -0
  288. package/packages/web/dist/assets/sieve-C3Gn_uJK.js +1 -0
  289. package/packages/web/dist/assets/simple-mode-GW_nhZxv.js +1 -0
  290. package/packages/web/dist/assets/smalltalk-CnHTOXQT.js +1 -0
  291. package/packages/web/dist/assets/solr-DehyRSwq.js +1 -0
  292. package/packages/web/dist/assets/sparql-DkYu6x3z.js +1 -0
  293. package/packages/web/dist/assets/spreadsheet-BCZA_wO0.js +1 -0
  294. package/packages/web/dist/assets/sql-D0XecflT.js +1 -0
  295. package/packages/web/dist/assets/stex-C3f8Ysf7.js +1 -0
  296. package/packages/web/dist/assets/style-BTin-zR_.css +1 -0
  297. package/packages/web/dist/assets/stylus-B533Al4x.js +1 -0
  298. package/packages/web/dist/assets/swift-BzpIVaGY.js +1 -0
  299. package/packages/web/dist/assets/tcl-DVfN8rqt.js +1 -0
  300. package/packages/web/dist/assets/textile-CnDTJFAw.js +1 -0
  301. package/packages/web/dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
  302. package/packages/web/dist/assets/tiki-DGYXhP31.js +1 -0
  303. package/packages/web/dist/assets/toml-Bm5Em-hy.js +1 -0
  304. package/packages/web/dist/assets/troff-wAsdV37c.js +1 -0
  305. package/packages/web/dist/assets/ttcn-CfJYG6tj.js +1 -0
  306. package/packages/web/dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
  307. package/packages/web/dist/assets/turtle-B1tBg_DP.js +1 -0
  308. package/packages/web/dist/assets/vb-CmGdzxic.js +1 -0
  309. package/packages/web/dist/assets/vbscript-BuJXcnF6.js +1 -0
  310. package/packages/web/dist/assets/velocity-D8B20fx6.js +1 -0
  311. package/packages/web/dist/assets/verilog-C6RDOZhf.js +1 -0
  312. package/packages/web/dist/assets/vhdl-lSbBsy5d.js +1 -0
  313. package/packages/web/dist/assets/webidl-ZXfAyPTL.js +1 -0
  314. package/packages/web/dist/assets/xquery-CQfU5ijd.js +1 -0
  315. package/packages/web/dist/assets/yacas-BJ4BC0dw.js +1 -0
  316. package/packages/web/dist/assets/z80-Hz9HOZM7.js +1 -0
  317. package/packages/web/dist/favicon.png +0 -0
  318. package/packages/web/dist/index.html +17 -0
  319. package/packages/web/dist/logo.png +0 -0
@@ -0,0 +1,552 @@
1
+ /**
2
+ * Session summary orchestration service.
3
+ *
4
+ * Coordinates summary generation, staleness detection, lifecycle hooks,
5
+ * and concurrency management. Delegates to extracted modules:
6
+ * - summaryClaudeClient.js — Claude SDK interaction
7
+ * - summaryPrompts.js — prompt templates, parsing, formatting
8
+ * - prUrlService.js — PR URL extraction, validation, enrichment
9
+ * - childSessionContext.js — child session hierarchy and file aggregation
10
+ * - withConcurrencyGuard.js — concurrency guard utility
11
+ * - summaryBroadcast.js — WebSocket broadcast helpers
12
+ */
13
+
14
+ import { sessions, messages, sessionSummaries, projects, settings } from '../database.js';
15
+ import { createConcurrencyGuard } from './withConcurrencyGuard.js';
16
+ import { callClaude } from './summaryClaudeClient.js';
17
+ import {
18
+ MAX_MESSAGES,
19
+ MIN_MESSAGES_FOR_SUMMARY,
20
+ MAX_RETRIES,
21
+ DEFAULT_SESSION_TITLE_PROMPT,
22
+ SUMMARY_SYSTEM_PROMPT,
23
+ formatMessages,
24
+ buildIncrementalPrompt,
25
+ parseSummaryResponse,
26
+ trackMessageMetadata,
27
+ stripMarkdownCodeBlock,
28
+ } from './summaryPrompts.js';
29
+ import { extractPrUrlIfNeeded, parsePrUrl, validatePrUrl, enrichPrData } from './prUrlService.js';
30
+ import { getChildSessions, buildChildSessionContext, aggregateFilesModified } from './childSessionContext.js';
31
+ import { broadcastSummaryUpdate, broadcastGeneratingStatus, broadcastSessionUpdate } from './summaryBroadcast.js';
32
+ import { isSummaryStale } from './summaryStaleCheck.js';
33
+
34
+ // Note: prStatusService is imported dynamically in onSessionComplete to avoid circular dependency
35
+
36
+ // Create the concurrency guard instance for summary generation
37
+ const guard = createConcurrencyGuard();
38
+
39
+ /**
40
+ * Generate summary for a session using Claude Code SDK (with concurrency guard)
41
+ * Only one generation can be in-flight per session at a time. If a generation is already
42
+ * running, the call is coalesced and a single follow-up generation is scheduled after completion.
43
+ * @param {string} sessionId
44
+ * @param {number} retryCount - Internal retry counter (do not set manually)
45
+ * @param {boolean} force - Force generation even if summary is current (default: false)
46
+ * @param {boolean} userInitiated - Whether triggered by explicit user action (default: false)
47
+ * @returns {Promise<Object|null>}
48
+ */
49
+ export async function generateSummary(sessionId, retryCount = 0, force = false, userInitiated = false) {
50
+ return guard.run(
51
+ sessionId,
52
+ () => _doGenerateSummary(sessionId, retryCount, force, userInitiated),
53
+ {
54
+ bypass: userInitiated,
55
+ onFollowUp: (key) => generateSummary(key),
56
+ }
57
+ );
58
+ }
59
+
60
+ /**
61
+ * Perform early-exit checks to decide whether summary generation should proceed.
62
+ * Returns an object with the decision and any data needed downstream.
63
+ * @param {string} sessionId
64
+ * @param {boolean} force
65
+ * @param {boolean} userInitiated
66
+ * @returns {{ skip: true, result: Object|null } | { skip: false, session: Object, globalSettings: Object, existingSummary: Object|null, allMessages: Array }}
67
+ */
68
+ function shouldGenerateSummary(sessionId, force, userInitiated) {
69
+ const session = sessions.getById(sessionId);
70
+ if (!session) {
71
+ console.warn(`[SummaryService] Session ${sessionId} not found for summary generation`);
72
+ return { skip: true, result: null };
73
+ }
74
+
75
+ const globalSettings = settings.getSummarySettings();
76
+ if (!userInitiated && globalSettings?.disableSessionSummaries) {
77
+ console.log(`[SummaryService] Session summaries disabled globally, skipping generation`);
78
+ return { skip: true, result: null };
79
+ }
80
+
81
+ const existingSummary = sessionSummaries.getBySessionId(sessionId);
82
+
83
+ if (existingSummary?.prMerged) {
84
+ console.log(`[SummaryService] Session ${sessionId} has merged PR, skipping regeneration`);
85
+ return { skip: true, result: existingSummary };
86
+ }
87
+
88
+ const allMessages = messages.getBySessionId(sessionId);
89
+
90
+ if (!force && !isSummaryStale(sessionId)) {
91
+ console.log(`[SummaryService] Summary for ${sessionId} is current, skipping regeneration`);
92
+ return { skip: true, result: existingSummary };
93
+ }
94
+
95
+ return { skip: false, session, globalSettings, existingSummary, allMessages };
96
+ }
97
+
98
+ /**
99
+ * Fetch conversation context: build child session context and the prompt for Claude.
100
+ * @param {string} sessionId
101
+ * @param {Object} context
102
+ * @param {Object|null} context.existingSummary
103
+ * @param {Array} context.recentMessages
104
+ * @param {Object} context.session
105
+ * @param {Object} context.globalSettings
106
+ * @returns {{ prompt: string, childContext: Object }}
107
+ */
108
+ function fetchConversationContext(sessionId, { existingSummary, recentMessages, session, globalSettings }) {
109
+ const childContext = buildChildSessionContext(sessionId);
110
+ const prompt = buildIncrementalPrompt(existingSummary, recentMessages, session.status, {
111
+ projectTitlePrompt: globalSettings?.sessionTitlePrompt,
112
+ childContext,
113
+ });
114
+ return { prompt, childContext };
115
+ }
116
+
117
+ /**
118
+ * Persist the generated summary and broadcast all related updates.
119
+ * Handles: metadata tracking, file aggregation, PR enrichment, upsert,
120
+ * project repo auto-population, session name/prUrl update, and broadcasts.
121
+ * @param {string} sessionId
122
+ * @param {Object} summaryDataInput - parsed summary data (mutated in place)
123
+ * @param {Object} session
124
+ * @param {Array} allMessages
125
+ * @returns {Promise<Object>} the persisted summary record
126
+ */
127
+ async function saveSummaryResult(sessionId, summaryDataInput, session, allMessages) {
128
+ const summaryData = summaryDataInput;
129
+ // Clean up internal flag before saving
130
+ delete summaryData._parseFailed;
131
+
132
+ // Add message count and last message ID for staleness tracking
133
+ trackMessageMetadata(summaryData, allMessages);
134
+
135
+ // For root sessions (no parent), aggregate files from all child sessions
136
+ if (!session.parentSessionId) {
137
+ summaryData.filesModified = aggregateFilesModified(sessionId, summaryData.filesModified);
138
+ }
139
+
140
+ // Validate and enrich PR URL
141
+ const prUrl = summaryData.prUrl || session.prUrl;
142
+ if (prUrl) {
143
+ const project = projects.getById(session.projectId);
144
+ await enrichPrData(summaryData, prUrl, project?.repoUrl, sessionId);
145
+ }
146
+
147
+ // Upsert summary
148
+ const summary = sessionSummaries.upsert(sessionId, summaryData);
149
+
150
+ // Auto-populate project repo URL if not already set
151
+ autoPopulateProjectRepoUrl(session, summaryData);
152
+
153
+ // Update session name and prUrl, then broadcast
154
+ updateSessionFromSummary(sessionId, session, summaryData);
155
+
156
+ // Broadcast updated summary to session and project subscribers
157
+ broadcastSummaryUpdate(sessionId, session.projectId, summary);
158
+
159
+ return summary;
160
+ }
161
+
162
+ /**
163
+ * Auto-populate the project's repoUrl from summary data if not already set.
164
+ * @param {Object} session
165
+ * @param {Object} summaryData
166
+ */
167
+ function autoPopulateProjectRepoUrl(session, summaryData) {
168
+ const project = projects.getById(session.projectId);
169
+ if (project.repoUrl || (!summaryData.prUrl && !summaryData.repositoryUrl)) return;
170
+
171
+ let extractedRepoUrl = summaryData.repositoryUrl;
172
+
173
+ if (!extractedRepoUrl && summaryData.prUrl) {
174
+ const prMatch = summaryData.prUrl.match(/^(https:\/\/github\.com\/[^/]+\/[^/]+)(?:\/|$)/);
175
+ if (prMatch) {
176
+ extractedRepoUrl = prMatch[1];
177
+ }
178
+ }
179
+
180
+ if (extractedRepoUrl) {
181
+ try {
182
+ projects.update(session.projectId, { repoUrl: extractedRepoUrl });
183
+ console.log(`[SummaryService] Auto-populated repo URL for project ${session.projectId}: ${extractedRepoUrl}`);
184
+ } catch (error) {
185
+ console.warn(`[SummaryService] Failed to auto-populate repo URL for project ${session.projectId}:`, error.message);
186
+ }
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Update session name and prUrl from summary data and broadcast changes.
192
+ * @param {string} sessionId
193
+ * @param {Object} session
194
+ * @param {Object} summaryData
195
+ */
196
+ function updateSessionFromSummary(sessionId, session, summaryData) {
197
+ if (summaryData.sessionTitle || summaryData.prUrl) {
198
+ const updateData = {};
199
+ const freshSession = sessions.getById(sessionId);
200
+
201
+ if (summaryData.sessionTitle && !freshSession.manuallyNamed) {
202
+ updateData.name = summaryData.sessionTitle;
203
+ }
204
+
205
+ if (summaryData.prUrl) {
206
+ updateData.prUrl = summaryData.prUrl;
207
+ }
208
+
209
+ const updatedSession = sessions.update(sessionId, updateData);
210
+
211
+ if (summaryData.prUrl) {
212
+ propagatePrUrlToParent(sessionId, summaryData.prUrl);
213
+ }
214
+
215
+ broadcastSessionUpdate(sessionId, session.projectId, updatedSession);
216
+ } else if (session.projectId) {
217
+ broadcastSessionUpdate(sessionId, session.projectId, sessions.getById(sessionId));
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Handle sessions with too few messages by creating a minimal summary.
223
+ * @param {string} sessionId
224
+ * @param {Object} session
225
+ * @param {Array} allMessages
226
+ * @returns {Object} the minimal summary
227
+ */
228
+ function createMinimalSummary(sessionId, session, allMessages) {
229
+ console.log(`[SummaryService] Session ${sessionId} has only ${allMessages.length} messages (minimum ${MIN_MESSAGES_FOR_SUMMARY}), creating minimal summary`);
230
+
231
+ const lastMessage = allMessages.length > 0 ? allMessages[allMessages.length - 1] : null;
232
+
233
+ const minimalSummary = {
234
+ shortSummary: 'Session in progress',
235
+ fullSummary: `Session with ${allMessages.length} message${allMessages.length !== 1 ? 's' : ''}`,
236
+ keyActions: [],
237
+ filesModified: [],
238
+ outcome: session.status === 'stopped' ? 'partial' : session.status === 'error' ? 'failed' : 'ongoing',
239
+ messageCount: allMessages.length,
240
+ lastSummarizedMessageId: lastMessage ? lastMessage.id : null,
241
+ };
242
+
243
+ const summary = sessionSummaries.upsert(sessionId, minimalSummary);
244
+
245
+ broadcastSummaryUpdate(sessionId, session.projectId, summary);
246
+
247
+ if (session.projectId) {
248
+ broadcastSessionUpdate(sessionId, session.projectId, sessions.getById(sessionId));
249
+ }
250
+
251
+ return summary;
252
+ }
253
+
254
+ /**
255
+ * Internal implementation of generateSummary (called by concurrency guard wrapper)
256
+ * @param {string} sessionId
257
+ * @param {number} retryCount
258
+ * @param {boolean} force
259
+ * @param {boolean} userInitiated
260
+ * @returns {Promise<Object|null>}
261
+ */
262
+ async function _doGenerateSummary(sessionId, retryCount = 0, force = false, userInitiated = false) {
263
+ try {
264
+ // Early-exit checks
265
+ const check = shouldGenerateSummary(sessionId, force, userInitiated);
266
+ if (check.skip) return check.result;
267
+
268
+ const { session, globalSettings, existingSummary, allMessages } = check;
269
+ const recentMessages = allMessages.slice(-MAX_MESSAGES);
270
+
271
+ // Broadcast that we're generating (do this early so UI always gets the event)
272
+ broadcastGeneratingStatus(sessionId, true);
273
+
274
+ // Handle sessions with too few messages
275
+ if (allMessages.length < MIN_MESSAGES_FOR_SUMMARY) {
276
+ const summary = createMinimalSummary(sessionId, session, allMessages);
277
+ broadcastGeneratingStatus(sessionId, false);
278
+ return summary;
279
+ }
280
+
281
+ // Build conversation context and prompt
282
+ const { prompt } = fetchConversationContext(sessionId, { existingSummary, recentMessages, session, globalSettings });
283
+
284
+ // Call Claude via SDK
285
+ const responseText = await callClaude(prompt, recentMessages, session.status, {
286
+ logMeta: { sessionId, callType: 'generateSessionSummary' },
287
+ systemPrompt: SUMMARY_SYSTEM_PROMPT,
288
+ });
289
+
290
+ // Parse response
291
+ const summaryData = parseSummaryResponse(responseText);
292
+
293
+ // Retry if parsing failed and we haven't exhausted retries
294
+ if (summaryData._parseFailed && retryCount < MAX_RETRIES) {
295
+ console.log(
296
+ `[SummaryService] Parse failed for session ${sessionId}, retrying (attempt ${retryCount + 2}/${MAX_RETRIES + 1})`
297
+ );
298
+ const backoffMs = 1000 * (retryCount + 1);
299
+ await new Promise((resolve) => setTimeout(resolve, backoffMs));
300
+ return _doGenerateSummary(sessionId, retryCount + 1, force, userInitiated);
301
+ }
302
+
303
+ // Persist summary and broadcast updates
304
+ const summary = await saveSummaryResult(sessionId, summaryData, session, allMessages);
305
+
306
+ // Clear the generating flag so the UI knows generation is complete
307
+ broadcastGeneratingStatus(sessionId, false);
308
+
309
+ console.log(`[SummaryService] Successfully generated summary for session ${sessionId}`);
310
+
311
+ // Propagate summary updates to parent sessions (workflow-aware)
312
+ if (session.parentSessionId) {
313
+ propagateToParent(sessionId);
314
+ }
315
+
316
+ return summary;
317
+ } catch (error) {
318
+ console.error(`[SummaryService] Failed to generate summary for session ${sessionId}:`, {
319
+ error: error.message,
320
+ stack: error.stack,
321
+ sessionId,
322
+ });
323
+
324
+ // Broadcast that generation stopped
325
+ broadcastGeneratingStatus(sessionId, false);
326
+
327
+ return null;
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Generate summary immediately and wait for completion (synchronous)
333
+ * Used by template trigger to ensure summary is ready before creating new session
334
+ * @param {string} sessionId
335
+ * @returns {Promise<Object|null>}
336
+ */
337
+ export async function generateSummaryNow(sessionId) {
338
+ // Check if session summaries are disabled globally (early exit before concurrency bookkeeping)
339
+ const globalSettings = settings.getSummarySettings();
340
+ if (globalSettings?.disableSessionSummaries) {
341
+ return null;
342
+ }
343
+ // Generate summary immediately and wait for completion
344
+ return generateSummary(sessionId);
345
+ }
346
+
347
+ /**
348
+ * Trigger summary generation on session activity (e.g., turn completion).
349
+ * @param {string} sessionId
350
+ */
351
+ export function onSessionActivity(sessionId) {
352
+ // Early exit if session summaries are disabled globally
353
+ const globalSettings = settings.getSummarySettings();
354
+ if (globalSettings?.disableSessionSummaries) {
355
+ return;
356
+ }
357
+
358
+ generateSummary(sessionId).catch((err) => {
359
+ console.error(`[SummaryService] Failed to generate summary on activity for session ${sessionId}:`, err);
360
+ });
361
+ }
362
+
363
+ /**
364
+ * Schedule CI status checks for a session with a PR.
365
+ * Uses dynamic import to avoid circular dependency.
366
+ * @param {string} sessionId
367
+ */
368
+ function scheduleCiChecks(sessionId) {
369
+ const scheduleCiCheck = async () => {
370
+ const prStatusService = await import('./prStatusService.js');
371
+ prStatusService.checkSessionCiStatusNow(sessionId);
372
+ };
373
+ // Check after 2 minutes (CI often takes a few minutes)
374
+ setTimeout(scheduleCiCheck, 2 * 60 * 1000);
375
+ // Check again after 5 minutes
376
+ setTimeout(scheduleCiCheck, 5 * 60 * 1000);
377
+ }
378
+
379
+ /**
380
+ * Map session status to summary outcome.
381
+ * @param {string} status - Session status
382
+ * @returns {'failed'|'partial'|'completed'}
383
+ */
384
+ function statusToOutcome(status) {
385
+ if (status === 'error') return 'failed';
386
+ if (status === 'stopped') return 'partial';
387
+ return 'completed';
388
+ }
389
+
390
+ /**
391
+ * Perform lightweight outcome-only update for a current summary.
392
+ * Returns true if handled (outcome unchanged or updated), false if summary generation needed.
393
+ * @param {string} sessionId
394
+ * @param {Object} existingSummary
395
+ * @param {Object} session
396
+ * @returns {boolean}
397
+ */
398
+ function tryLightweightOutcomeUpdate(sessionId, existingSummary, session) {
399
+ if (!existingSummary || isSummaryStale(sessionId) || !session) {
400
+ return false;
401
+ }
402
+
403
+ const newOutcome = statusToOutcome(session.status);
404
+ if (existingSummary.outcome === newOutcome) {
405
+ console.log(`[SummaryService] Summary for session ${sessionId} is current and outcome unchanged, skipping generation`);
406
+ return true;
407
+ }
408
+
409
+ sessionSummaries.upsert(sessionId, { ...existingSummary, outcome: newOutcome });
410
+ console.log(`[SummaryService] Lightweight outcome update for session ${sessionId}: ${existingSummary.outcome} -> ${newOutcome}`);
411
+
412
+ const updatedSummary = sessionSummaries.getBySessionId(sessionId);
413
+ broadcastSummaryUpdate(sessionId, session.projectId, updatedSummary);
414
+ return true;
415
+ }
416
+
417
+ /**
418
+ * Called when session completes - generate immediately if summary is stale,
419
+ * otherwise do a lightweight outcome-only DB update (no LLM call).
420
+ * Also schedules follow-up CI checks for sessions with PRs.
421
+ * @param {string} sessionId
422
+ */
423
+ export function onSessionComplete(sessionId) {
424
+ const globalSettings = settings.getSummarySettings();
425
+ const session = sessions.getById(sessionId);
426
+
427
+ // Schedule CI checks if session has a PR (always, regardless of summary settings)
428
+ if (session?.prUrl) {
429
+ scheduleCiChecks(sessionId);
430
+ }
431
+
432
+ // Early exit if session summaries are disabled globally
433
+ if (globalSettings?.disableSessionSummaries) {
434
+ return;
435
+ }
436
+
437
+ // Try lightweight outcome update first
438
+ const existingSummary = sessionSummaries.getBySessionId(sessionId);
439
+ if (tryLightweightOutcomeUpdate(sessionId, existingSummary, session)) {
440
+ return;
441
+ }
442
+
443
+ // Summary is stale or doesn't exist -- generate via LLM
444
+ generateSummary(sessionId);
445
+ }
446
+
447
+ /**
448
+ * Get summary for a session, generating if needed
449
+ * @param {string} sessionId
450
+ * @param {boolean} generateIfMissing - Whether to generate if no summary exists
451
+ * @returns {Promise<Object|null>}
452
+ */
453
+ export async function getSummary(sessionId, generateIfMissing = false) {
454
+ let summary = sessionSummaries.getBySessionId(sessionId);
455
+
456
+ if (!summary && generateIfMissing) {
457
+ // Don't generate if session summaries are disabled globally
458
+ const globalSettings = settings.getSummarySettings();
459
+ if (globalSettings?.disableSessionSummaries) return null;
460
+ summary = await generateSummary(sessionId);
461
+ }
462
+
463
+ return summary;
464
+ }
465
+
466
+ /**
467
+ * Force regenerate summary for a session (user-initiated action)
468
+ * This bypasses the global disable setting since the user explicitly requested it.
469
+ * @param {string} sessionId
470
+ * @returns {Promise<Object|null>}
471
+ */
472
+ export async function regenerateSummary(sessionId) {
473
+ return generateSummary(sessionId, 0, true, true);
474
+ }
475
+
476
+ // Re-export isSummaryStale from summaryStaleCheck.js for backward compatibility
477
+ export { isSummaryStale };
478
+
479
+ /**
480
+ * Clean up any in-flight state for a session (call on session deletion)
481
+ * @param {string} sessionId
482
+ */
483
+ export function cleanupSession(sessionId) {
484
+ guard.cleanup(sessionId);
485
+ }
486
+
487
+ /**
488
+ * Propagate summary update to parent sessions
489
+ * @param {string} sessionId - The child session ID that was updated
490
+ */
491
+ export async function propagateToParent(sessionId) {
492
+ const session = sessions.getById(sessionId);
493
+ if (!session || !session.parentSessionId) return;
494
+
495
+ generateSummary(session.parentSessionId);
496
+ }
497
+
498
+ /**
499
+ * Propagate PR URL from a child session to its root session.
500
+ * Walks up the parent chain to find the root and sets the PR URL there.
501
+ * Only sets the root's prUrl if it doesn't already have one.
502
+ * @param {string} sessionId - The child session that received a PR URL
503
+ * @param {string} prUrl - The PR URL to propagate
504
+ */
505
+ export function propagatePrUrlToParent(sessionId, prUrl) {
506
+ if (!prUrl) return;
507
+
508
+ const session = sessions.getById(sessionId);
509
+ if (!session || !session.parentSessionId) return; // already root or orphan
510
+
511
+ const rootId = sessions.getRootSessionId(sessionId);
512
+ if (!rootId || rootId === sessionId) return;
513
+
514
+ const root = sessions.getById(rootId);
515
+ if (!root || root.prUrl) return; // Don't overwrite existing PR URL
516
+
517
+ sessions.update(root.id, { prUrl });
518
+
519
+ // Broadcast updates
520
+ broadcastSessionUpdate(root.id, root.projectId, sessions.getById(root.id));
521
+
522
+ console.log(`[SummaryService] Propagated PR URL from session ${sessionId} to root ${root.id}: ${prUrl}`);
523
+ }
524
+
525
+ // Re-export from extracted modules for backward compatibility
526
+ // These are used by external consumers and tests
527
+ export {
528
+ // From summaryPrompts.js
529
+ MAX_MESSAGES,
530
+ MIN_MESSAGES_FOR_SUMMARY,
531
+ MAX_RETRIES,
532
+ DEFAULT_SESSION_TITLE_PROMPT,
533
+ SUMMARY_SYSTEM_PROMPT,
534
+ formatMessages,
535
+ buildIncrementalPrompt,
536
+ parseSummaryResponse,
537
+ stripMarkdownCodeBlock as _stripMarkdownCodeBlock,
538
+ trackMessageMetadata as _trackMessageMetadata,
539
+ };
540
+
541
+ // From summaryClaudeClient.js
542
+ export { callClaude };
543
+
544
+ // From prUrlService.js
545
+ export { parsePrUrl, validatePrUrl, extractPrUrlIfNeeded, enrichPrData as _enrichPrData };
546
+
547
+ // From childSessionContext.js
548
+ export { getChildSessions, buildChildSessionContext, aggregateFilesModified };
549
+
550
+ // Read-only accessors for concurrency guard state
551
+ export const isGenerationActive = (key) => guard.isActive(key);
552
+ export const isRegenerationPending = (key) => guard.isPending(key);
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Staleness detection for session summaries.
3
+ * Extracted from summaryService.js for modularity.
4
+ */
5
+
6
+ import { sessionSummaries, messages } from '../database.js';
7
+
8
+ /**
9
+ * Check if a summary is stale (message count or last message ID has changed)
10
+ * @param {string} sessionId
11
+ * @returns {boolean}
12
+ */
13
+ export function isSummaryStale(sessionId) {
14
+ const summary = sessionSummaries.getBySessionId(sessionId);
15
+ if (!summary) return true;
16
+
17
+ const allMessages = messages.getBySessionId(sessionId);
18
+
19
+ // Use message ID-based staleness detection if available
20
+ if (summary.lastSummarizedMessageId) {
21
+ const lastMessage = allMessages.length > 0 ? allMessages[allMessages.length - 1] : null;
22
+ const lastMessageId = lastMessage ? lastMessage.id : null;
23
+
24
+ // Summary is stale if the last message ID doesn't match
25
+ if (lastMessageId !== summary.lastSummarizedMessageId) {
26
+ return true;
27
+ }
28
+
29
+ // Also validate count as a secondary check (defensive programming)
30
+ return allMessages.length !== summary.messageCount;
31
+ }
32
+
33
+ // Fallback to count-based staleness detection for old summaries
34
+ return allMessages.length !== summary.messageCount;
35
+ }