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,204 @@
|
|
|
1
|
+
import { sessions, sessionSummaries } from '../database.js';
|
|
2
|
+
import { webSocketManager, broadcastToSession } from '../websocket.js';
|
|
3
|
+
import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
|
|
4
|
+
import * as ghService from './ghService.js';
|
|
5
|
+
|
|
6
|
+
// Default polling interval in milliseconds (1 minute)
|
|
7
|
+
const DEFAULT_POLL_INTERVAL_MS = 60000;
|
|
8
|
+
|
|
9
|
+
// PR states that don't need further polling (PR is done)
|
|
10
|
+
const FINAL_PR_STATES = ['merged', 'closed'];
|
|
11
|
+
|
|
12
|
+
// Session states that should have their PRs polled (allowlist for future-proofing)
|
|
13
|
+
// Includes stopped because CI may still be running after session ends
|
|
14
|
+
// When 'archived' is added, it won't be in this list
|
|
15
|
+
const POLLABLE_SESSION_STATES = ['running', 'waiting', 'stopped'];
|
|
16
|
+
|
|
17
|
+
// Time-based filtering constants
|
|
18
|
+
// Sessions updated within this time are polled regardless of subscribers
|
|
19
|
+
const RECENT_ACTIVITY_MS = 2 * 60 * 60 * 1000; // 2 hours
|
|
20
|
+
// Maximum age for polling sessions (even with pending CI)
|
|
21
|
+
const MAX_POLL_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
22
|
+
|
|
23
|
+
// Polling interval handle
|
|
24
|
+
let pollIntervalId = null;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Start the PR status polling service
|
|
28
|
+
*/
|
|
29
|
+
export function start() {
|
|
30
|
+
if (pollIntervalId) {
|
|
31
|
+
console.log('[PrStatusService] Already running');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log('[PrStatusService] Starting PR status polling');
|
|
36
|
+
pollIntervalId = setInterval(pollLoop, DEFAULT_POLL_INTERVAL_MS);
|
|
37
|
+
|
|
38
|
+
// Run immediately on start
|
|
39
|
+
pollLoop();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Stop the PR status polling service
|
|
44
|
+
*/
|
|
45
|
+
export function stop() {
|
|
46
|
+
if (pollIntervalId) {
|
|
47
|
+
clearInterval(pollIntervalId);
|
|
48
|
+
pollIntervalId = null;
|
|
49
|
+
console.log('[PrStatusService] Stopped PR status polling');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get all sessions with PRs that should be checked
|
|
55
|
+
* Uses time-based filtering to poll recently active sessions without requiring subscribers
|
|
56
|
+
* @returns {Array<{sessionId: string, prUrl: string}>}
|
|
57
|
+
*/
|
|
58
|
+
export function getSessionsToCheck() {
|
|
59
|
+
// Get all sessions that have PR URLs
|
|
60
|
+
const sessionsWithPrs = sessions.getSessionsWithPrUrls();
|
|
61
|
+
const subscriptions = webSocketManager.getSessionSubscriptions();
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
const result = [];
|
|
64
|
+
|
|
65
|
+
for (const session of sessionsWithPrs) {
|
|
66
|
+
// Skip if session is not in a pollable state (future-proofs for 'archived')
|
|
67
|
+
if (!POLLABLE_SESSION_STATES.includes(session.status)) continue;
|
|
68
|
+
|
|
69
|
+
// Skip if PR is in a final state (merged/closed)
|
|
70
|
+
const summary = sessionSummaries.getBySessionId(session.id);
|
|
71
|
+
if (summary?.prState && FINAL_PR_STATES.includes(summary.prState)) continue;
|
|
72
|
+
|
|
73
|
+
// Calculate session age based on updatedAt
|
|
74
|
+
const sessionAge = now - new Date(session.updatedAt).getTime();
|
|
75
|
+
const hasSubscribers = subscriptions.get(session.id)?.size > 0;
|
|
76
|
+
|
|
77
|
+
// Always poll sessions with active subscribers
|
|
78
|
+
if (hasSubscribers) {
|
|
79
|
+
result.push({ sessionId: session.id, prUrl: session.prUrl });
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Skip sessions older than max poll age
|
|
84
|
+
if (sessionAge > MAX_POLL_AGE_MS) continue;
|
|
85
|
+
|
|
86
|
+
// For sessions within recent activity window, poll without subscribers
|
|
87
|
+
if (sessionAge <= RECENT_ACTIVITY_MS) {
|
|
88
|
+
result.push({ sessionId: session.id, prUrl: session.prUrl });
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// For older sessions without subscribers, only poll if CI is pending
|
|
93
|
+
if (summary?.ciStatus === 'pending') {
|
|
94
|
+
result.push({ sessionId: session.id, prUrl: session.prUrl });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Immediately check CI status for a specific session
|
|
103
|
+
* Used for on-demand checks when a session completes or PR URL is set
|
|
104
|
+
* @param {string} sessionId
|
|
105
|
+
* @returns {Promise<boolean>} Whether status was updated
|
|
106
|
+
*/
|
|
107
|
+
export async function checkSessionCiStatusNow(sessionId) {
|
|
108
|
+
const session = sessions.getById(sessionId);
|
|
109
|
+
if (!session?.prUrl) return false;
|
|
110
|
+
return checkPrStatus(sessionId, session.prUrl);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Normalize boolean values for comparison (handles null/undefined/false equivalence)
|
|
115
|
+
* SQLite stores booleans as 0/1, and the repository maps 0 to null in some cases.
|
|
116
|
+
* This ensures null, undefined, and false are all treated as equivalent.
|
|
117
|
+
* @param {boolean|null|undefined} value
|
|
118
|
+
* @returns {boolean}
|
|
119
|
+
*/
|
|
120
|
+
function normalizeBool(value) {
|
|
121
|
+
return Boolean(value);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Check and update PR status for a single session
|
|
126
|
+
* @param {string} sessionId
|
|
127
|
+
* @param {string} prUrl
|
|
128
|
+
* @returns {Promise<boolean>} Whether status was updated
|
|
129
|
+
*/
|
|
130
|
+
export async function checkPrStatus(sessionId, prUrl) {
|
|
131
|
+
try {
|
|
132
|
+
const prInfo = await ghService.getPrInfo(prUrl);
|
|
133
|
+
if (!prInfo) return false;
|
|
134
|
+
|
|
135
|
+
const currentSummary = sessionSummaries.getBySessionId(sessionId);
|
|
136
|
+
|
|
137
|
+
// Check if anything changed
|
|
138
|
+
// Note: Use normalizeBool for boolean fields because SQLite stores 0/1 and
|
|
139
|
+
// the repository maps 0 to null, so we need to treat null/false as equivalent
|
|
140
|
+
const hasChanged =
|
|
141
|
+
currentSummary?.prState !== prInfo.state ||
|
|
142
|
+
normalizeBool(currentSummary?.prMerged) !== normalizeBool(prInfo.merged) ||
|
|
143
|
+
normalizeBool(currentSummary?.hasMergeConflicts) !== normalizeBool(prInfo.hasMergeConflicts) ||
|
|
144
|
+
currentSummary?.ciStatus !== prInfo.ciStatus ||
|
|
145
|
+
JSON.stringify(currentSummary?.ciFailures || []) !== JSON.stringify(prInfo.ciFailures || []);
|
|
146
|
+
|
|
147
|
+
if (!hasChanged) return false;
|
|
148
|
+
|
|
149
|
+
// Build update data
|
|
150
|
+
const updateData = {
|
|
151
|
+
prState: prInfo.state,
|
|
152
|
+
prMerged: prInfo.merged,
|
|
153
|
+
hasMergeConflicts: prInfo.hasMergeConflicts,
|
|
154
|
+
ciStatus: prInfo.ciStatus,
|
|
155
|
+
ciFailures: prInfo.ciFailures,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// Don't create a summary just for PR tracking - only track if summary already exists
|
|
159
|
+
if (!currentSummary) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Update database
|
|
164
|
+
const updatedSummary = sessionSummaries.upsert(sessionId, updateData);
|
|
165
|
+
|
|
166
|
+
// Broadcast change
|
|
167
|
+
broadcastToSession(sessionId, WS_MESSAGE_TYPES.SESSION_SUMMARY_UPDATED, {
|
|
168
|
+
sessionId,
|
|
169
|
+
summary: updatedSummary,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
console.log(`[PrStatusService] Updated PR status for session ${sessionId}: ${prInfo.state}`);
|
|
173
|
+
return true;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error(`[PrStatusService] Error checking PR for session ${sessionId}:`, error.message);
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Main polling loop - called every interval
|
|
182
|
+
*/
|
|
183
|
+
async function pollLoop() {
|
|
184
|
+
const sessionsToCheck = getSessionsToCheck();
|
|
185
|
+
|
|
186
|
+
if (sessionsToCheck.length === 0) return;
|
|
187
|
+
|
|
188
|
+
console.log(`[PrStatusService] Checking ${sessionsToCheck.length} session(s) with PRs`);
|
|
189
|
+
|
|
190
|
+
// Check sequentially to avoid hammering GitHub API
|
|
191
|
+
for (const { sessionId, prUrl } of sessionsToCheck) {
|
|
192
|
+
await checkPrStatus(sessionId, prUrl);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Export for testing
|
|
197
|
+
export {
|
|
198
|
+
DEFAULT_POLL_INTERVAL_MS,
|
|
199
|
+
FINAL_PR_STATES,
|
|
200
|
+
POLLABLE_SESSION_STATES,
|
|
201
|
+
RECENT_ACTIVITY_MS,
|
|
202
|
+
MAX_POLL_AGE_MS,
|
|
203
|
+
pollLoop,
|
|
204
|
+
};
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PR URL extraction, parsing, validation, and enrichment.
|
|
3
|
+
* Consolidates all PR-related functionality from summaryService.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { sessions, messages } from '../database.js';
|
|
7
|
+
import { broadcastSessionUpdate } from './summaryBroadcast.js';
|
|
8
|
+
import * as ghService from './ghService.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parse a GitHub PR URL into components
|
|
12
|
+
* @param {string} prUrl - GitHub PR URL
|
|
13
|
+
* @returns {Object|null} - { owner, repo, number } or null if invalid format
|
|
14
|
+
*/
|
|
15
|
+
export function parsePrUrl(prUrl) {
|
|
16
|
+
if (!prUrl) return null;
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// Match GitHub PR URL pattern: https://github.com/{owner}/{repo}/pull/{number}
|
|
20
|
+
const match = prUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)$/);
|
|
21
|
+
if (!match) {
|
|
22
|
+
console.warn(`[PrUrlService] Invalid PR URL format: ${prUrl}`);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
owner: match[1],
|
|
28
|
+
repo: match[2],
|
|
29
|
+
number: parseInt(match[3], 10),
|
|
30
|
+
};
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.warn(`[PrUrlService] Failed to parse PR URL ${prUrl}:`, error.message);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Validate that a PR URL belongs to the expected repository
|
|
39
|
+
* @param {string} prUrl - GitHub PR URL
|
|
40
|
+
* @param {string} expectedRepoUrl - Expected repository URL
|
|
41
|
+
* @returns {Object} - { valid: boolean, prComponents: Object|null, mismatch: boolean, error: string|null }
|
|
42
|
+
*/
|
|
43
|
+
export function validatePrUrl(prUrl, expectedRepoUrl) {
|
|
44
|
+
if (!prUrl) {
|
|
45
|
+
return { valid: false, prComponents: null, mismatch: false, error: 'No PR URL provided' };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Parse the PR URL
|
|
49
|
+
const prComponents = parsePrUrl(prUrl);
|
|
50
|
+
if (!prComponents) {
|
|
51
|
+
return { valid: false, prComponents: null, mismatch: false, error: 'Invalid PR URL format' };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// If no expected repo URL, we can't validate the match - accept it but log a warning
|
|
55
|
+
if (!expectedRepoUrl) {
|
|
56
|
+
console.warn(`[PrUrlService] No expected repo URL to validate against PR: ${prUrl}`);
|
|
57
|
+
return { valid: true, prComponents, mismatch: false, error: null };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Extract owner/repo from expected repo URL
|
|
61
|
+
const expectedMatch = expectedRepoUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/?$/);
|
|
62
|
+
if (!expectedMatch) {
|
|
63
|
+
console.warn(`[PrUrlService] Invalid expected repo URL format: ${expectedRepoUrl}`);
|
|
64
|
+
return { valid: true, prComponents, mismatch: false, error: null };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const expectedOwner = expectedMatch[1];
|
|
68
|
+
const expectedRepo = expectedMatch[2];
|
|
69
|
+
|
|
70
|
+
// Validate that the PR belongs to the expected repository
|
|
71
|
+
const ownerMatch = prComponents.owner === expectedOwner;
|
|
72
|
+
const repoMatch = prComponents.repo === expectedRepo;
|
|
73
|
+
|
|
74
|
+
if (!ownerMatch || !repoMatch) {
|
|
75
|
+
console.warn(
|
|
76
|
+
`[PrUrlService] PR repository mismatch: ` +
|
|
77
|
+
`PR is from ${prComponents.owner}/${prComponents.repo}, ` +
|
|
78
|
+
`but expected ${expectedOwner}/${expectedRepo}`
|
|
79
|
+
);
|
|
80
|
+
return {
|
|
81
|
+
valid: false,
|
|
82
|
+
prComponents,
|
|
83
|
+
mismatch: true,
|
|
84
|
+
error: `PR from ${prComponents.owner}/${prComponents.repo} does not match expected ${expectedOwner}/${expectedRepo}`
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { valid: true, prComponents, mismatch: false, error: null };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Extract PR URL from session messages by scanning for GitHub PR links
|
|
93
|
+
* @param {string} sessionId - The session ID
|
|
94
|
+
* @returns {string|null} - The PR URL if found, null otherwise
|
|
95
|
+
*/
|
|
96
|
+
export function extractPrUrlFromMessages(sessionId) {
|
|
97
|
+
const allMessages = messages.getBySessionId(sessionId);
|
|
98
|
+
if (!allMessages || allMessages.length === 0) return null;
|
|
99
|
+
|
|
100
|
+
// Get recent messages (last 20) to scan for PR URLs
|
|
101
|
+
const recentMessages = allMessages.slice(-20);
|
|
102
|
+
|
|
103
|
+
// GitHub PR URL pattern: https://github.com/{owner}/{repo}/pull/{number}
|
|
104
|
+
const prUrlPattern = /https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+/g;
|
|
105
|
+
|
|
106
|
+
// Scan messages in reverse order (most recent first) to find the latest PR URL
|
|
107
|
+
for (let i = recentMessages.length - 1; i >= 0; i--) {
|
|
108
|
+
const message = recentMessages[i];
|
|
109
|
+
const matches = message.content?.match(prUrlPattern);
|
|
110
|
+
|
|
111
|
+
if (matches && matches.length > 0) {
|
|
112
|
+
// Return the most recent PR URL found
|
|
113
|
+
return matches[matches.length - 1];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Extract PR URL from recent messages immediately after a turn completes.
|
|
122
|
+
* This is lightweight (no Claude API call) - just scans messages for URLs.
|
|
123
|
+
* @param {string} sessionId - The session ID
|
|
124
|
+
*/
|
|
125
|
+
export async function extractPrUrlIfNeeded(sessionId) {
|
|
126
|
+
const session = sessions.getById(sessionId);
|
|
127
|
+
if (!session) return;
|
|
128
|
+
|
|
129
|
+
// Skip if session already has a PR URL
|
|
130
|
+
if (session.prUrl) return;
|
|
131
|
+
|
|
132
|
+
// Extract PR URL from messages
|
|
133
|
+
const prUrl = extractPrUrlFromMessages(sessionId);
|
|
134
|
+
if (prUrl) {
|
|
135
|
+
sessions.update(sessionId, { prUrl });
|
|
136
|
+
console.log(`[PrUrlService] Extracted PR URL for session ${sessionId}: ${prUrl}`);
|
|
137
|
+
|
|
138
|
+
// Set session name from PR title (async, fire-and-forget)
|
|
139
|
+
await setSessionNameFromPr(sessionId, prUrl);
|
|
140
|
+
|
|
141
|
+
// Broadcast session update so UI shows PR URL immediately
|
|
142
|
+
broadcastSessionUpdate(sessionId, session.projectId, sessions.getById(sessionId));
|
|
143
|
+
|
|
144
|
+
// Propagate PR URL to root session
|
|
145
|
+
if (session.parentSessionId) {
|
|
146
|
+
const rootId = sessions.getRootSessionId(sessionId);
|
|
147
|
+
const root = rootId && rootId !== sessionId ? sessions.getById(rootId) : null;
|
|
148
|
+
if (root && !root.prUrl) {
|
|
149
|
+
sessions.update(root.id, { prUrl });
|
|
150
|
+
console.log(`[PrUrlService] Propagated PR URL from session ${sessionId} to root ${root.id}: ${prUrl}`);
|
|
151
|
+
|
|
152
|
+
// Broadcast root session update
|
|
153
|
+
broadcastSessionUpdate(root.id, root.projectId, sessions.getById(root.id));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Validate and enrich PR URL data with GitHub PR status information
|
|
161
|
+
* @param {Object} summaryDataInput - Summary data to enrich (modified in place)
|
|
162
|
+
* @param {string} prUrl - PR URL to validate and enrich
|
|
163
|
+
* @param {string} projectRepoUrl - Expected repository URL for validation
|
|
164
|
+
* @param {string} sessionId - Session ID for logging
|
|
165
|
+
*/
|
|
166
|
+
export async function enrichPrData(summaryDataInput, prUrl, projectRepoUrl, sessionId) {
|
|
167
|
+
const summaryData = summaryDataInput;
|
|
168
|
+
const validation = validatePrUrl(prUrl, projectRepoUrl);
|
|
169
|
+
|
|
170
|
+
if (!validation.valid) {
|
|
171
|
+
console.warn(`[PrUrlService] PR URL validation failed for session ${sessionId}:`, validation.error);
|
|
172
|
+
summaryData.prUrl = null;
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const prInfo = await ghService.getPrInfo(prUrl);
|
|
178
|
+
if (prInfo) {
|
|
179
|
+
summaryData.prState = prInfo.state;
|
|
180
|
+
summaryData.prMerged = prInfo.merged;
|
|
181
|
+
summaryData.hasMergeConflicts = prInfo.hasMergeConflicts;
|
|
182
|
+
summaryData.ciStatus = prInfo.ciStatus;
|
|
183
|
+
if (prInfo.ciFailures !== undefined) {
|
|
184
|
+
summaryData.ciFailures = prInfo.ciFailures;
|
|
185
|
+
}
|
|
186
|
+
// Add PR title to summary data
|
|
187
|
+
if (prInfo.title) {
|
|
188
|
+
summaryData.prTitle = prInfo.title;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.warn(`[PrUrlService] Failed to get PR info for ${prUrl}:`, error.message);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Set session name from PR title when PR URL is associated.
|
|
198
|
+
* @param {string} sessionId - The session ID
|
|
199
|
+
* @param {string} prUrl - The PR URL
|
|
200
|
+
*/
|
|
201
|
+
export async function setSessionNameFromPr(sessionId, prUrl) {
|
|
202
|
+
const session = sessions.getById(sessionId);
|
|
203
|
+
if (!session || session.manuallyNamed) return; // Don't overwrite if manually named
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
const prInfo = await ghService.getPrInfo(prUrl);
|
|
207
|
+
if (prInfo?.title) {
|
|
208
|
+
sessions.update(sessionId, {
|
|
209
|
+
name: prInfo.title,
|
|
210
|
+
manuallyNamed: true, // Protect from summary overwrites
|
|
211
|
+
});
|
|
212
|
+
console.log(`[PrUrlService] Set session ${sessionId} name from PR title: "${prInfo.title}"`);
|
|
213
|
+
|
|
214
|
+
// Broadcast the update
|
|
215
|
+
const updated = sessions.getById(sessionId);
|
|
216
|
+
broadcastSessionUpdate(sessionId, session.projectId, updated);
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.warn(`[PrUrlService] Failed to set session name from PR ${prUrl}:`, error.message);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Backward-compatible alias for internal/test usage
|
|
224
|
+
export { enrichPrData as _enrichPrData };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test a provider configuration by making a minimal API call
|
|
5
|
+
* @param {Object} config - Provider configuration to test
|
|
6
|
+
* @param {string} [config.baseUrl] - Base URL for the provider
|
|
7
|
+
* @param {string} [config.authToken] - Auth token for the provider
|
|
8
|
+
* @param {string} [config.defaultSonnetModel] - Default Sonnet model ID
|
|
9
|
+
* @param {number} [config.apiTimeoutMs] - API timeout in milliseconds
|
|
10
|
+
* @returns {Promise<{success: boolean, message: string, details?: Object}>}
|
|
11
|
+
*/
|
|
12
|
+
export async function testProviderConnection(config) {
|
|
13
|
+
const { baseUrl, authToken, defaultSonnetModel, apiTimeoutMs } = config;
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
// Build client options
|
|
17
|
+
const clientOptions = {};
|
|
18
|
+
|
|
19
|
+
if (baseUrl) {
|
|
20
|
+
clientOptions.baseURL = baseUrl;
|
|
21
|
+
}
|
|
22
|
+
if (authToken) {
|
|
23
|
+
clientOptions.apiKey = authToken;
|
|
24
|
+
}
|
|
25
|
+
if (apiTimeoutMs) {
|
|
26
|
+
clientOptions.timeout = apiTimeoutMs;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const client = new Anthropic(clientOptions);
|
|
30
|
+
|
|
31
|
+
// Use a minimal message to test connectivity
|
|
32
|
+
// This verifies: network, auth, and model availability
|
|
33
|
+
const testModel = defaultSonnetModel || 'claude-sonnet-4-20250514';
|
|
34
|
+
|
|
35
|
+
const response = await client.messages.create({
|
|
36
|
+
model: testModel,
|
|
37
|
+
max_tokens: 10,
|
|
38
|
+
messages: [{ role: 'user', content: 'Hi' }],
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
success: true,
|
|
43
|
+
message: 'Connection successful',
|
|
44
|
+
details: {
|
|
45
|
+
model: response.model,
|
|
46
|
+
usage: response.usage,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return {
|
|
51
|
+
success: false,
|
|
52
|
+
message: getErrorMessage(error),
|
|
53
|
+
details: {
|
|
54
|
+
code: error.status || error.code,
|
|
55
|
+
type: error.type || error.name,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get a human-readable error message from an error object
|
|
63
|
+
* @param {Error} error - The error object
|
|
64
|
+
* @returns {string} - Human-readable error message
|
|
65
|
+
* @private
|
|
66
|
+
*/
|
|
67
|
+
function getErrorMessage(error) {
|
|
68
|
+
if (error.status === 401) {
|
|
69
|
+
return 'Authentication failed. Check your auth token.';
|
|
70
|
+
}
|
|
71
|
+
if (error.status === 404) {
|
|
72
|
+
return 'Model not found. Check the model ID.';
|
|
73
|
+
}
|
|
74
|
+
if (error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED') {
|
|
75
|
+
return 'Could not connect to server. Check the base URL.';
|
|
76
|
+
}
|
|
77
|
+
if (error.code === 'ETIMEDOUT') {
|
|
78
|
+
return 'Connection timed out. Try increasing the timeout.';
|
|
79
|
+
}
|
|
80
|
+
return error.message || 'Unknown error occurred';
|
|
81
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { sessions } from '../database.js';
|
|
2
|
+
import { broadcastToSession, broadcastToProject } from '../websocket.js';
|
|
3
|
+
import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Custom error class for schedule operations, includes HTTP status code.
|
|
7
|
+
*/
|
|
8
|
+
export class ScheduleError extends Error {
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} message
|
|
11
|
+
* @param {number} statusCode
|
|
12
|
+
*/
|
|
13
|
+
constructor(message, statusCode) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = 'ScheduleError';
|
|
16
|
+
this.statusCode = statusCode;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Extract and transform scheduling options from request data.
|
|
22
|
+
* @param {object} scheduleData - Raw schedule data from request
|
|
23
|
+
* @returns {object} Transformed scheduling options
|
|
24
|
+
*/
|
|
25
|
+
function extractSchedulingOptions(scheduleData) {
|
|
26
|
+
const options = {};
|
|
27
|
+
const boolFields = ['autoRescheduleEnabled', 'rescheduleOnTokenLimit', 'rescheduleOnServiceError'];
|
|
28
|
+
const intFields = ['rescheduleDelayMinutes'];
|
|
29
|
+
const nullableIntFields = ['maxRescheduleCount', 'maxTotalTokens', 'rescheduleAtTokenCount'];
|
|
30
|
+
|
|
31
|
+
for (const field of boolFields) {
|
|
32
|
+
if (scheduleData[field] !== undefined) {
|
|
33
|
+
options[field] = Boolean(scheduleData[field]);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
for (const field of intFields) {
|
|
37
|
+
if (scheduleData[field] !== undefined) {
|
|
38
|
+
options[field] = parseInt(scheduleData[field], 10);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const field of nullableIntFields) {
|
|
42
|
+
if (scheduleData[field] !== undefined) {
|
|
43
|
+
options[field] = scheduleData[field] ? parseInt(scheduleData[field], 10) : null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (scheduleData.pendingModel !== undefined) {
|
|
47
|
+
options.pendingModel = scheduleData.pendingModel;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return options;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Configures a schedule for a follow-up message on an existing session.
|
|
55
|
+
*
|
|
56
|
+
* @param {object} session - The session object (from req.session_)
|
|
57
|
+
* @param {object} scheduleData
|
|
58
|
+
* @param {number} scheduleData.scheduledAt - Future timestamp in ms
|
|
59
|
+
* @param {string} [scheduleData.pendingModel] - Model to use when scheduled run fires
|
|
60
|
+
* @param {boolean} [scheduleData.autoRescheduleEnabled] - Enable auto-rescheduling
|
|
61
|
+
* @param {number} [scheduleData.rescheduleDelayMinutes] - Delay between reschedules
|
|
62
|
+
* @param {boolean} [scheduleData.rescheduleOnTokenLimit] - Reschedule on token limit errors
|
|
63
|
+
* @param {boolean} [scheduleData.rescheduleOnServiceError] - Reschedule on service errors
|
|
64
|
+
* @param {number|null} [scheduleData.maxRescheduleCount] - Max number of reschedules
|
|
65
|
+
* @param {number|null} [scheduleData.maxTotalTokens] - Max total tokens before stopping
|
|
66
|
+
* @param {number|null} [scheduleData.rescheduleAtTokenCount] - Token count trigger for reschedule
|
|
67
|
+
* @returns {object} The updated session
|
|
68
|
+
*/
|
|
69
|
+
export function configureSchedule(session, scheduleData) {
|
|
70
|
+
// Validate session status
|
|
71
|
+
if (!['waiting', 'stopped', 'error'].includes(session.status)) {
|
|
72
|
+
throw new ScheduleError('Session must be in waiting, stopped, or error state to schedule', 400);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const { scheduledAt } = scheduleData;
|
|
76
|
+
|
|
77
|
+
// Validate scheduledAt is provided and in the future
|
|
78
|
+
if (!scheduledAt || scheduledAt <= Date.now()) {
|
|
79
|
+
throw new ScheduleError('scheduledAt must be a future timestamp', 400);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Validate that pendingPrompt exists and is not empty
|
|
83
|
+
if (!session.pendingPrompt || session.pendingPrompt.trim() === '') {
|
|
84
|
+
throw new ScheduleError('pendingPrompt must be set before scheduling', 400);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Build update data with scheduling options
|
|
88
|
+
const updateData = {
|
|
89
|
+
status: 'scheduled',
|
|
90
|
+
scheduledAt,
|
|
91
|
+
...extractSchedulingOptions(scheduleData),
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const updated = sessions.update(session.id, updateData);
|
|
95
|
+
|
|
96
|
+
// Broadcast status update
|
|
97
|
+
broadcastToSession(session.id, WS_MESSAGE_TYPES.SESSION_STATUS, {
|
|
98
|
+
sessionId: session.id,
|
|
99
|
+
status: 'scheduled',
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Broadcast session update to project subscribers
|
|
103
|
+
broadcastToProject(session.projectId, WS_MESSAGE_TYPES.SESSION_UPDATED, {
|
|
104
|
+
projectId: session.projectId,
|
|
105
|
+
sessionId: session.id,
|
|
106
|
+
session: updated,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return updated;
|
|
110
|
+
}
|