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.
- package/package.json +33 -0
- package/packages/server/bin/cli.js +4 -0
- package/packages/server/src/agents/AgentGateway.js +64 -0
- package/packages/server/src/agents/BaseAgent.js +41 -0
- package/packages/server/src/agents/LoggingAgentWrapper.js +73 -0
- package/packages/server/src/agents/adapters/ClaudeCodeAdapter.js +33 -0
- package/packages/server/src/agents/adapters/CodexAdapter.js +26 -0
- package/packages/server/src/agents/types.js +43 -0
- package/packages/server/src/agents/vcr/CassetteStore.js +111 -0
- package/packages/server/src/agents/vcr/VCRAgentAdapter.js +126 -0
- package/packages/server/src/agents/vcr/VCRSummaryWrapper.js +71 -0
- package/packages/server/src/api/canvas-helpers.js +249 -0
- package/packages/server/src/api/canvas-trash-routes.js +205 -0
- package/packages/server/src/api/canvas.js +331 -0
- package/packages/server/src/api/commandButtons.js +312 -0
- package/packages/server/src/api/commands.js +169 -0
- package/packages/server/src/api/filesystem.js +62 -0
- package/packages/server/src/api/git.js +85 -0
- package/packages/server/src/api/index.js +44 -0
- package/packages/server/src/api/kanban.js +342 -0
- package/packages/server/src/api/metrics.js +194 -0
- package/packages/server/src/api/projects-helpers.js +43 -0
- package/packages/server/src/api/projects-session-helpers.js +295 -0
- package/packages/server/src/api/projects.js +384 -0
- package/packages/server/src/api/providers.js +249 -0
- package/packages/server/src/api/quickResponses.js +129 -0
- package/packages/server/src/api/sessions-archive.js +69 -0
- package/packages/server/src/api/sessions-commands.js +220 -0
- package/packages/server/src/api/sessions-conversations.js +168 -0
- package/packages/server/src/api/sessions-draft.js +72 -0
- package/packages/server/src/api/sessions-lifecycle.js +190 -0
- package/packages/server/src/api/sessions-messages.js +141 -0
- package/packages/server/src/api/sessions-notes.js +51 -0
- package/packages/server/src/api/sessions-patch.js +252 -0
- package/packages/server/src/api/sessions-streaming.js +86 -0
- package/packages/server/src/api/sessions.js +269 -0
- package/packages/server/src/api/settings.js +194 -0
- package/packages/server/src/api/templates.js +63 -0
- package/packages/server/src/app.js +51 -0
- package/packages/server/src/database.js +58 -0
- package/packages/server/src/db/AgentCallLogRepository.js +322 -0
- package/packages/server/src/db/AttachmentRepository.js +191 -0
- package/packages/server/src/db/BaseRepository.js +39 -0
- package/packages/server/src/db/CanvasItemRepository.js +315 -0
- package/packages/server/src/db/CommandButtonRepository.js +75 -0
- package/packages/server/src/db/CommandRunRepository.js +219 -0
- package/packages/server/src/db/ConversationRepository.js +379 -0
- package/packages/server/src/db/DatabaseManager.js +91 -0
- package/packages/server/src/db/KanbanBoardRepository.js +92 -0
- package/packages/server/src/db/KanbanCardRepository.js +286 -0
- package/packages/server/src/db/KanbanLaneRepository.js +279 -0
- package/packages/server/src/db/MessageRepository.js +156 -0
- package/packages/server/src/db/ProjectDefaultsRepository.js +173 -0
- package/packages/server/src/db/ProjectRepository.js +110 -0
- package/packages/server/src/db/ProviderRepository.js +307 -0
- package/packages/server/src/db/QuickResponseRepository.js +186 -0
- package/packages/server/src/db/SessionNoteRepository.js +60 -0
- package/packages/server/src/db/SessionRepository.js +314 -0
- package/packages/server/src/db/SessionSummaryRepository.js +200 -0
- package/packages/server/src/db/SessionTemplateRepository.js +171 -0
- package/packages/server/src/db/SettingsRepository.js +211 -0
- package/packages/server/src/db/TodoRepository.js +132 -0
- package/packages/server/src/db/WorkLogRepository.js +122 -0
- package/packages/server/src/db/conversation-helpers.js +119 -0
- package/packages/server/src/db/index.js +100 -0
- package/packages/server/src/db/migrations/canvasItemsMigrations.js +109 -0
- package/packages/server/src/db/migrations/conversationsMigrations.js +183 -0
- package/packages/server/src/db/migrations/index.js +199 -0
- package/packages/server/src/db/migrations/kanbanMigrations.js +99 -0
- package/packages/server/src/db/migrations/migrationUtils.js +55 -0
- package/packages/server/src/db/migrations/miscMigrations.js +242 -0
- package/packages/server/src/db/migrations/projectsMigrations.js +95 -0
- package/packages/server/src/db/migrations/sessionsMigrations.js +282 -0
- package/packages/server/src/db/session-helpers.js +150 -0
- package/packages/server/src/index.js +106 -0
- package/packages/server/src/logger.js +22 -0
- package/packages/server/src/middleware/sessionLookup.js +57 -0
- package/packages/server/src/middleware/upload.js +94 -0
- package/packages/server/src/schema.sql +363 -0
- package/packages/server/src/services/agentCallLogger.js +116 -0
- package/packages/server/src/services/canvasStore.js +56 -0
- package/packages/server/src/services/childSessionContext.js +61 -0
- package/packages/server/src/services/commandRunner.js +422 -0
- package/packages/server/src/services/conversationContext.js +72 -0
- package/packages/server/src/services/diffService.js +172 -0
- package/packages/server/src/services/draftSessionService.js +181 -0
- package/packages/server/src/services/encryption.js +134 -0
- package/packages/server/src/services/ghService.js +169 -0
- package/packages/server/src/services/gitService.js +520 -0
- package/packages/server/src/services/gitSessionSetup.js +48 -0
- package/packages/server/src/services/hookService.js +60 -0
- package/packages/server/src/services/kanbanService.js +262 -0
- package/packages/server/src/services/kanbanTriggers.js +273 -0
- package/packages/server/src/services/nodeSpawnHelper.js +63 -0
- package/packages/server/src/services/prStatusService.js +204 -0
- package/packages/server/src/services/prUrlService.js +224 -0
- package/packages/server/src/services/providerTestService.js +81 -0
- package/packages/server/src/services/scheduleService.js +110 -0
- package/packages/server/src/services/schedulerService.js +281 -0
- package/packages/server/src/services/sessionDuplicator.js +63 -0
- package/packages/server/src/services/sessionErrors.js +173 -0
- package/packages/server/src/services/sessionExecution.js +378 -0
- package/packages/server/src/services/sessionManager.js +356 -0
- package/packages/server/src/services/sessionPrompts.js +427 -0
- package/packages/server/src/services/sessionProvider.js +107 -0
- package/packages/server/src/services/slashCommandDiscovery.js +258 -0
- package/packages/server/src/services/slashCommandPluginDiscovery.js +216 -0
- package/packages/server/src/services/slashCommandService.js +306 -0
- package/packages/server/src/services/streamEventCallbacks.js +170 -0
- package/packages/server/src/services/streamEventHandler.js +488 -0
- package/packages/server/src/services/streamUsageHandler.js +228 -0
- package/packages/server/src/services/summaryBroadcast.js +61 -0
- package/packages/server/src/services/summaryClaudeClient.js +180 -0
- package/packages/server/src/services/summaryPrompts.js +169 -0
- package/packages/server/src/services/summaryService.js +552 -0
- package/packages/server/src/services/summaryStaleCheck.js +35 -0
- package/packages/server/src/services/systemMonitor.js +281 -0
- package/packages/server/src/services/templateTriggerService.js +197 -0
- package/packages/server/src/services/terminalOutput.js +160 -0
- package/packages/server/src/services/todoStore.js +58 -0
- package/packages/server/src/services/usageTracker.js +69 -0
- package/packages/server/src/services/withConcurrencyGuard.js +110 -0
- package/packages/server/src/websocket.js +10 -0
- package/packages/server/src/ws/WebSocketManager.js +240 -0
- package/packages/server/src/ws/index.js +50 -0
- package/packages/shared/package.json +27 -0
- package/packages/shared/src/constants.js +44 -0
- package/packages/shared/src/contracts/canvas.js +25 -0
- package/packages/shared/src/contracts/commandButtons.js +36 -0
- package/packages/shared/src/contracts/kanban.js +142 -0
- package/packages/shared/src/contracts/projects.js +63 -0
- package/packages/shared/src/contracts/providers.js +81 -0
- package/packages/shared/src/contracts/quickResponses.js +44 -0
- package/packages/shared/src/contracts/sessions.js +112 -0
- package/packages/shared/src/contracts/templates.js +51 -0
- package/packages/shared/src/index.js +5 -0
- package/packages/shared/src/protocol.js +76 -0
- package/packages/shared/src/routeParams.js +36 -0
- package/packages/shared/src/types.js +167 -0
- package/packages/shared/src/utils.js +101 -0
- package/packages/web/dist/assets/ActiveSessionsView-BQc76Jc8.js +1 -0
- package/packages/web/dist/assets/ActiveSessionsView-ofSvx-K1.css +1 -0
- package/packages/web/dist/assets/AgentLogsView-CTCjHjsu.js +2 -0
- package/packages/web/dist/assets/AgentLogsView-D90PnQVk.css +1 -0
- package/packages/web/dist/assets/ApiClient-Dbs1H78V.js +1 -0
- package/packages/web/dist/assets/ArchiveConfirmModal-CCxSZ52u.js +1 -0
- package/packages/web/dist/assets/ArchiveConfirmModal-CQZeuYBz.css +1 -0
- package/packages/web/dist/assets/CommandButtonDetailView-CF_-LXpU.js +1 -0
- package/packages/web/dist/assets/CommandButtonDetailView-DBm3rzhw.css +1 -0
- package/packages/web/dist/assets/EffortLevelSelector-BQaQmU2d.css +1 -0
- package/packages/web/dist/assets/EffortLevelSelector-DPofLvm-.js +1 -0
- package/packages/web/dist/assets/GeneralSettingsView-BCf53fpC.css +1 -0
- package/packages/web/dist/assets/GeneralSettingsView-BY1G-Kv8.js +1 -0
- package/packages/web/dist/assets/InterpolationHelp-CgdbNcJB.js +1 -0
- package/packages/web/dist/assets/InterpolationHelp-iNxTxmhs.css +1 -0
- package/packages/web/dist/assets/MarkdownEditor-CqT1U8lo.js +2 -0
- package/packages/web/dist/assets/MarkdownEditor-enuH2yvP.css +1 -0
- package/packages/web/dist/assets/ModelSelector-BBn_Ve0D.js +1 -0
- package/packages/web/dist/assets/ModelSelector-DPPD-92R.css +1 -0
- package/packages/web/dist/assets/NewSessionView-Bo5l49nu.js +3 -0
- package/packages/web/dist/assets/NewSessionView-Byoi1XdQ.css +1 -0
- package/packages/web/dist/assets/PathChooser-BoMGzeg2.css +1 -0
- package/packages/web/dist/assets/PathChooser-Cx9gQ-Qt.js +1 -0
- package/packages/web/dist/assets/ProjectEditView-BFuscj-V.js +1 -0
- package/packages/web/dist/assets/ProjectEditView-DNwBUNRk.css +1 -0
- package/packages/web/dist/assets/ProjectListView-C55H1JHQ.css +1 -0
- package/packages/web/dist/assets/ProjectListView-Dj0jBZ46.js +1 -0
- package/packages/web/dist/assets/ProjectNewView-Brdp-xUu.js +1 -0
- package/packages/web/dist/assets/ProjectNewView-CpgE4R-l.css +1 -0
- package/packages/web/dist/assets/ProvidersView-B_QQF3RM.css +1 -0
- package/packages/web/dist/assets/ProvidersView-Cxc-1skq.js +1 -0
- package/packages/web/dist/assets/QuickResponseSettings-B2eVAtHW.js +1 -0
- package/packages/web/dist/assets/QuickResponseSettings-B8188A1D.css +1 -0
- package/packages/web/dist/assets/QuickResponsesPanel-DIBQFj0W.css +1 -0
- package/packages/web/dist/assets/QuickResponsesPanel-lU8pW2B0.js +1 -0
- package/packages/web/dist/assets/ResizableTextarea-B5nAA0RV.css +1 -0
- package/packages/web/dist/assets/ResizableTextarea-DSy1mWGY.js +1 -0
- package/packages/web/dist/assets/SessionCard-BvjLwVYg.js +1 -0
- package/packages/web/dist/assets/SessionCard-D20G3bX8.css +1 -0
- package/packages/web/dist/assets/SessionDetailView-BQbPg-RJ.js +36 -0
- package/packages/web/dist/assets/SessionDetailView-BrMG4p2-.css +1 -0
- package/packages/web/dist/assets/SessionFormOptions-BgqFR-5f.js +1 -0
- package/packages/web/dist/assets/SessionFormOptions-BuLlDF-7.css +1 -0
- package/packages/web/dist/assets/SessionListView-BAIBtJF7.css +1 -0
- package/packages/web/dist/assets/SessionListView-CYIHI8qF.js +1 -0
- package/packages/web/dist/assets/SessionLogStream-B-FwUMJQ.js +18 -0
- package/packages/web/dist/assets/SessionLogStream-zPUTiGbe.css +1 -0
- package/packages/web/dist/assets/SettingsView-DC8-hTQ-.css +1 -0
- package/packages/web/dist/assets/SettingsView-fZxpiGp7.js +1 -0
- package/packages/web/dist/assets/SlashCommandWizard-BB30cSvo.css +1 -0
- package/packages/web/dist/assets/SlashCommandWizard-BgaOw9W3.js +1 -0
- package/packages/web/dist/assets/SummarySettingsView-DcsmSVJI.css +1 -0
- package/packages/web/dist/assets/SummarySettingsView-eeu1Xq86.js +1 -0
- package/packages/web/dist/assets/TemplateDetailView-DEPKSwDo.js +1 -0
- package/packages/web/dist/assets/TemplateDetailView-DT2m06W7.css +1 -0
- package/packages/web/dist/assets/apl-B4CMkyY2.js +1 -0
- package/packages/web/dist/assets/asciiarmor-Df11BRmG.js +1 -0
- package/packages/web/dist/assets/asn1-EdZsLKOL.js +1 -0
- package/packages/web/dist/assets/asterisk-B-8jnY81.js +1 -0
- package/packages/web/dist/assets/brainfuck-C4LP7Hcl.js +1 -0
- package/packages/web/dist/assets/clike-B9uivgTg.js +1 -0
- package/packages/web/dist/assets/clojure-BMjYHr_A.js +1 -0
- package/packages/web/dist/assets/cmake-BQqOBYOt.js +1 -0
- package/packages/web/dist/assets/cobol-CWcv1MsR.js +1 -0
- package/packages/web/dist/assets/coffeescript-S37ZYGWr.js +1 -0
- package/packages/web/dist/assets/commandButtons-DNSHH8IA.js +4 -0
- package/packages/web/dist/assets/commonlisp-DBKNyK5s.js +1 -0
- package/packages/web/dist/assets/crystal-SjHAIU92.js +1 -0
- package/packages/web/dist/assets/css-BnMrqG3P.js +1 -0
- package/packages/web/dist/assets/cypher-C_CwsFkJ.js +1 -0
- package/packages/web/dist/assets/d-pRatUO7H.js +1 -0
- package/packages/web/dist/assets/diff-DbItnlRl.js +1 -0
- package/packages/web/dist/assets/dockerfile-BKs6k2Af.js +1 -0
- package/packages/web/dist/assets/dtd-DF_7sFjM.js +1 -0
- package/packages/web/dist/assets/dylan-DwRh75JA.js +1 -0
- package/packages/web/dist/assets/ebnf-CDyGwa7X.js +1 -0
- package/packages/web/dist/assets/ecl-Cabwm37j.js +1 -0
- package/packages/web/dist/assets/eiffel-CnydiIhH.js +1 -0
- package/packages/web/dist/assets/elm-vLlmbW-K.js +1 -0
- package/packages/web/dist/assets/erlang-BNw1qcRV.js +1 -0
- package/packages/web/dist/assets/factor-kuTfRLto.js +1 -0
- package/packages/web/dist/assets/fcl-Kvtd6kyn.js +1 -0
- package/packages/web/dist/assets/forth-Ffai-XNe.js +1 -0
- package/packages/web/dist/assets/fortran-DYz_wnZ1.js +1 -0
- package/packages/web/dist/assets/gas-Bneqetm1.js +1 -0
- package/packages/web/dist/assets/gherkin-heZmZLOM.js +1 -0
- package/packages/web/dist/assets/groovy-D9Dt4D0W.js +1 -0
- package/packages/web/dist/assets/haskell-BWDZoCOh.js +1 -0
- package/packages/web/dist/assets/haxe-H-WmDvRZ.js +1 -0
- package/packages/web/dist/assets/http-DBlCnlav.js +1 -0
- package/packages/web/dist/assets/idl-BEugSyMb.js +1 -0
- package/packages/web/dist/assets/index-BZlHgDSz.js +1 -0
- package/packages/web/dist/assets/index-BhWX8AfE.js +2 -0
- package/packages/web/dist/assets/index-Bi3XvF_f.js +1 -0
- package/packages/web/dist/assets/index-BqXoPf_D.js +1 -0
- package/packages/web/dist/assets/index-CAuTOZSD.js +1 -0
- package/packages/web/dist/assets/index-CKYk-fkb.js +1 -0
- package/packages/web/dist/assets/index-CTumW_tV.js +318 -0
- package/packages/web/dist/assets/index-CVOJVSsC.js +82 -0
- package/packages/web/dist/assets/index-CXK2Z3_z.js +1 -0
- package/packages/web/dist/assets/index-CYllQ3Vd.js +1 -0
- package/packages/web/dist/assets/index-CpsfI08O.js +1 -0
- package/packages/web/dist/assets/index-DQkhDeTA.js +3 -0
- package/packages/web/dist/assets/index-DWP8iCBp.js +1 -0
- package/packages/web/dist/assets/index-DkVb9W_J.js +1 -0
- package/packages/web/dist/assets/index-DmKHPbIa.js +1 -0
- package/packages/web/dist/assets/index-DrlQi03X.js +1 -0
- package/packages/web/dist/assets/index-gmCCsCQ1.css +1 -0
- package/packages/web/dist/assets/index-prTEzzgO.js +1 -0
- package/packages/web/dist/assets/index-wqgejMCM.js +1 -0
- package/packages/web/dist/assets/index-yh0ZHIWw.js +7 -0
- package/packages/web/dist/assets/javascript-qCveANmP.js +1 -0
- package/packages/web/dist/assets/julia-DuME0IfC.js +1 -0
- package/packages/web/dist/assets/livescript-BwQOo05w.js +1 -0
- package/packages/web/dist/assets/lua-BgMRiT3U.js +1 -0
- package/packages/web/dist/assets/mathematica-DTrFuWx2.js +1 -0
- package/packages/web/dist/assets/mbox-CNhZ1qSd.js +1 -0
- package/packages/web/dist/assets/mirc-CjQqDB4T.js +1 -0
- package/packages/web/dist/assets/mllike-CXdrOF99.js +1 -0
- package/packages/web/dist/assets/modelica-Dc1JOy9r.js +1 -0
- package/packages/web/dist/assets/mscgen-BA5vi2Kp.js +1 -0
- package/packages/web/dist/assets/mumps-BT43cFF4.js +1 -0
- package/packages/web/dist/assets/nginx-DdIZxoE0.js +1 -0
- package/packages/web/dist/assets/nsis-LdVXkNf5.js +1 -0
- package/packages/web/dist/assets/ntriples-BfvgReVJ.js +1 -0
- package/packages/web/dist/assets/octave-Ck1zUtKM.js +1 -0
- package/packages/web/dist/assets/oz-BzwKVEFT.js +1 -0
- package/packages/web/dist/assets/pascal--L3eBynH.js +1 -0
- package/packages/web/dist/assets/perl-CdXCOZ3F.js +1 -0
- package/packages/web/dist/assets/pig-CevX1Tat.js +1 -0
- package/packages/web/dist/assets/powershell-CFHJl5sT.js +1 -0
- package/packages/web/dist/assets/projects-DbBQQH-V.js +1 -0
- package/packages/web/dist/assets/properties-C78fOPTZ.js +1 -0
- package/packages/web/dist/assets/protobuf-ChK-085T.js +1 -0
- package/packages/web/dist/assets/providers-ceCc4xRU.js +1 -0
- package/packages/web/dist/assets/pug-DukmZTjD.js +1 -0
- package/packages/web/dist/assets/puppet-DMA9R1ak.js +1 -0
- package/packages/web/dist/assets/python-BuPzkPfP.js +1 -0
- package/packages/web/dist/assets/q-pXgVlZs6.js +1 -0
- package/packages/web/dist/assets/r-DUYO_cvP.js +1 -0
- package/packages/web/dist/assets/rpm-CTu-6PCP.js +1 -0
- package/packages/web/dist/assets/ruby-B2Rjki9n.js +1 -0
- package/packages/web/dist/assets/sas-B4kiWyti.js +1 -0
- package/packages/web/dist/assets/scheme-C41bIUwD.js +1 -0
- package/packages/web/dist/assets/sessions-D681M81k.js +1 -0
- package/packages/web/dist/assets/settings-D0evez2V.js +1 -0
- package/packages/web/dist/assets/shell-CjFT_Tl9.js +1 -0
- package/packages/web/dist/assets/sieve-C3Gn_uJK.js +1 -0
- package/packages/web/dist/assets/simple-mode-GW_nhZxv.js +1 -0
- package/packages/web/dist/assets/smalltalk-CnHTOXQT.js +1 -0
- package/packages/web/dist/assets/solr-DehyRSwq.js +1 -0
- package/packages/web/dist/assets/sparql-DkYu6x3z.js +1 -0
- package/packages/web/dist/assets/spreadsheet-BCZA_wO0.js +1 -0
- package/packages/web/dist/assets/sql-D0XecflT.js +1 -0
- package/packages/web/dist/assets/stex-C3f8Ysf7.js +1 -0
- package/packages/web/dist/assets/style-BTin-zR_.css +1 -0
- package/packages/web/dist/assets/stylus-B533Al4x.js +1 -0
- package/packages/web/dist/assets/swift-BzpIVaGY.js +1 -0
- package/packages/web/dist/assets/tcl-DVfN8rqt.js +1 -0
- package/packages/web/dist/assets/textile-CnDTJFAw.js +1 -0
- package/packages/web/dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
- package/packages/web/dist/assets/tiki-DGYXhP31.js +1 -0
- package/packages/web/dist/assets/toml-Bm5Em-hy.js +1 -0
- package/packages/web/dist/assets/troff-wAsdV37c.js +1 -0
- package/packages/web/dist/assets/ttcn-CfJYG6tj.js +1 -0
- package/packages/web/dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
- package/packages/web/dist/assets/turtle-B1tBg_DP.js +1 -0
- package/packages/web/dist/assets/vb-CmGdzxic.js +1 -0
- package/packages/web/dist/assets/vbscript-BuJXcnF6.js +1 -0
- package/packages/web/dist/assets/velocity-D8B20fx6.js +1 -0
- package/packages/web/dist/assets/verilog-C6RDOZhf.js +1 -0
- package/packages/web/dist/assets/vhdl-lSbBsy5d.js +1 -0
- package/packages/web/dist/assets/webidl-ZXfAyPTL.js +1 -0
- package/packages/web/dist/assets/xquery-CQfU5ijd.js +1 -0
- package/packages/web/dist/assets/yacas-BJ4BC0dw.js +1 -0
- package/packages/web/dist/assets/z80-Hz9HOZM7.js +1 -0
- package/packages/web/dist/favicon.png +0 -0
- package/packages/web/dist/index.html +17 -0
- 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
|
+
}
|