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,181 @@
|
|
|
1
|
+
import { sessions, messages, projects, conversations, attachments } from '../database.js';
|
|
2
|
+
import { broadcastToSession, broadcastToProject } from '../websocket.js';
|
|
3
|
+
import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
|
|
4
|
+
import * as slashCommandService from './slashCommandService.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Validates that a session is a draft (waiting status with no assistant messages).
|
|
8
|
+
* @param {object} session - The session object
|
|
9
|
+
* @returns {{ valid: boolean, error?: string }}
|
|
10
|
+
*/
|
|
11
|
+
export function validateDraftSession(session) {
|
|
12
|
+
if (session.status !== 'waiting') {
|
|
13
|
+
return { valid: false, error: 'Session must be in waiting status to start' };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const allMessages = messages.getBySessionId(session.id);
|
|
17
|
+
const hasAssistantMessages = allMessages.some(msg => msg.role === 'assistant');
|
|
18
|
+
if (hasAssistantMessages) {
|
|
19
|
+
return { valid: false, error: 'Session is not a draft - it already has responses' };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return { valid: true };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Validate that a prompt is non-empty.
|
|
27
|
+
* @param {*} prompt
|
|
28
|
+
* @returns {boolean}
|
|
29
|
+
*/
|
|
30
|
+
function isValidPrompt(prompt) {
|
|
31
|
+
return prompt && typeof prompt === 'string' && prompt.trim() !== '';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create the initial user message for a draft session.
|
|
36
|
+
* @param {object} session
|
|
37
|
+
* @param {string} promptToUse
|
|
38
|
+
* @returns {object} The created message
|
|
39
|
+
*/
|
|
40
|
+
function createInitialMessage(session, promptToUse) {
|
|
41
|
+
const activeConv = conversations.getActiveBySessionId(session.id);
|
|
42
|
+
if (!activeConv) {
|
|
43
|
+
throw new DraftSessionError('No active conversation found', 500);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const initialMessage = messages.create(session.id, 'user', promptToUse, { toolUse: null, conversationId: activeConv.id });
|
|
47
|
+
sessions.update(session.id, { pendingPrompt: null });
|
|
48
|
+
|
|
49
|
+
broadcastToSession(session.id, WS_MESSAGE_TYPES.MESSAGE_CREATED, {
|
|
50
|
+
sessionId: session.id,
|
|
51
|
+
message: initialMessage,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return initialMessage;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Update an existing initial message with a new prompt.
|
|
59
|
+
* @param {object} session
|
|
60
|
+
* @param {object} initialMessage
|
|
61
|
+
* @param {string} newPrompt
|
|
62
|
+
* @returns {object} The updated message
|
|
63
|
+
*/
|
|
64
|
+
function updateInitialMessage(session, initialMessage, newPrompt) {
|
|
65
|
+
const updatedMessage = messages.updateContent(initialMessage.id, newPrompt);
|
|
66
|
+
|
|
67
|
+
broadcastToSession(session.id, WS_MESSAGE_TYPES.MESSAGE_UPDATED, {
|
|
68
|
+
sessionId: session.id,
|
|
69
|
+
message: updatedMessage,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return updatedMessage;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get or create the initial user message for a draft session.
|
|
77
|
+
* @param {object} session
|
|
78
|
+
* @param {object} options
|
|
79
|
+
* @returns {object} The initial message
|
|
80
|
+
*/
|
|
81
|
+
function getOrCreateInitialMessage(session, options) {
|
|
82
|
+
const allMessages = messages.getBySessionId(session.id);
|
|
83
|
+
const userMessages = allMessages.filter(msg => msg.role === 'user');
|
|
84
|
+
|
|
85
|
+
// No existing user messages - create one
|
|
86
|
+
if (userMessages.length === 0) {
|
|
87
|
+
const promptToUse = options.prompt || session.pendingPrompt;
|
|
88
|
+
if (!isValidPrompt(promptToUse)) {
|
|
89
|
+
throw new DraftSessionError('No initial prompt found', 400);
|
|
90
|
+
}
|
|
91
|
+
return createInitialMessage(session, promptToUse);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Existing user message - optionally update it
|
|
95
|
+
let initialMessage = userMessages[0];
|
|
96
|
+
if (options.prompt !== undefined) {
|
|
97
|
+
if (!isValidPrompt(options.prompt)) {
|
|
98
|
+
throw new DraftSessionError('Prompt must be a non-empty string', 400);
|
|
99
|
+
}
|
|
100
|
+
initialMessage = updateInitialMessage(session, initialMessage, options.prompt);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return initialMessage;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Starts a draft session by resolving the prompt, creating messages if needed,
|
|
108
|
+
* and kicking off the session manager.
|
|
109
|
+
*
|
|
110
|
+
* @param {object} session - The session object (from req.session_)
|
|
111
|
+
* @param {object} options
|
|
112
|
+
* @param {string} [options.prompt] - Optional new prompt to use/override
|
|
113
|
+
* @param {string} [options.model] - Optional model override
|
|
114
|
+
* @returns {Promise<object>} The updated session
|
|
115
|
+
*/
|
|
116
|
+
export async function startDraft(session, options = {}) {
|
|
117
|
+
const project = projects.getById(session.projectId);
|
|
118
|
+
if (!project) {
|
|
119
|
+
throw new DraftSessionError('Project not found', 404);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Use gitWorktree if set, otherwise use project's working directory
|
|
123
|
+
const workingDirectory = session.gitWorktree || project.workingDirectory;
|
|
124
|
+
|
|
125
|
+
// Model to use for this session (optional - SDK will use default if not provided)
|
|
126
|
+
const model = options.model || session.pendingModel || session.model || null;
|
|
127
|
+
|
|
128
|
+
// Get or create the initial user message
|
|
129
|
+
const initialMessage = getOrCreateInitialMessage(session, options);
|
|
130
|
+
const finalPrompt = initialMessage.content;
|
|
131
|
+
|
|
132
|
+
// Get session attachments for context
|
|
133
|
+
const sessionAttachments = attachments.getBySessionId(session.id);
|
|
134
|
+
|
|
135
|
+
// Update session status to starting and clear pendingModel (mirrors pendingPrompt cleanup above)
|
|
136
|
+
sessions.update(session.id, { status: 'starting', pendingModel: null });
|
|
137
|
+
|
|
138
|
+
// Resolve skill/command invocations so skill body goes into system prompt
|
|
139
|
+
const resolved = await slashCommandService.resolvePromptSkillOrCommand(
|
|
140
|
+
workingDirectory, finalPrompt, project.systemPrompt
|
|
141
|
+
);
|
|
142
|
+
const effectivePrompt = resolved ? resolved.userMessage : finalPrompt;
|
|
143
|
+
const effectiveSystemPrompt = resolved ? resolved.systemPrompt : project.systemPrompt;
|
|
144
|
+
|
|
145
|
+
// Start session manager (non-blocking)
|
|
146
|
+
const { runSession } = await import('./sessionManager.js');
|
|
147
|
+
runSession(session.id, effectivePrompt, workingDirectory, { systemPrompt: effectiveSystemPrompt, fileAttachments: sessionAttachments, model }).catch((error) => {
|
|
148
|
+
console.error('Session error:', error);
|
|
149
|
+
sessions.update(session.id, { status: 'error', error: error.message });
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Broadcast status update
|
|
153
|
+
broadcastToSession(session.id, WS_MESSAGE_TYPES.SESSION_STATUS, {
|
|
154
|
+
sessionId: session.id,
|
|
155
|
+
status: 'starting',
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Broadcast to project subscribers
|
|
159
|
+
broadcastToProject(session.projectId, WS_MESSAGE_TYPES.SESSION_UPDATED, {
|
|
160
|
+
projectId: session.projectId,
|
|
161
|
+
sessionId: session.id,
|
|
162
|
+
session: sessions.getById(session.id),
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
return sessions.getById(session.id);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Custom error class for draft session operations, includes HTTP status code.
|
|
170
|
+
*/
|
|
171
|
+
export class DraftSessionError extends Error {
|
|
172
|
+
/**
|
|
173
|
+
* @param {string} message
|
|
174
|
+
* @param {number} statusCode
|
|
175
|
+
*/
|
|
176
|
+
constructor(message, statusCode) {
|
|
177
|
+
super(message);
|
|
178
|
+
this.name = 'DraftSessionError';
|
|
179
|
+
this.statusCode = statusCode;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
|
|
6
|
+
const ALGORITHM = 'aes-256-gcm';
|
|
7
|
+
const KEY_LENGTH = 32; // 256 bits for AES-256
|
|
8
|
+
const IV_LENGTH = 12; // 96 bits is recommended for GCM
|
|
9
|
+
const SEPARATOR = ':';
|
|
10
|
+
const PARTS_COUNT = 3;
|
|
11
|
+
|
|
12
|
+
// Allow tests to override the key directory so they never touch the real ~/.circuschief/secret.key
|
|
13
|
+
let _keyDirOverride = null;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get or generate the encryption key.
|
|
17
|
+
* Uses ~/.circuschief/secret.key file (auto-generated on first run)
|
|
18
|
+
* @returns {Buffer} - 32-byte encryption key
|
|
19
|
+
*/
|
|
20
|
+
function getEncryptionKey() {
|
|
21
|
+
const keyDir = _keyDirOverride || join(homedir(), '.circuschief');
|
|
22
|
+
const keyPath = join(keyDir, 'secret.key');
|
|
23
|
+
|
|
24
|
+
if (existsSync(keyPath)) {
|
|
25
|
+
const keyHex = readFileSync(keyPath, 'utf-8').trim();
|
|
26
|
+
// Validate the key is a 64-char hex string (32 bytes)
|
|
27
|
+
if (/^[0-9a-fA-F]{64}$/.test(keyHex)) {
|
|
28
|
+
return Buffer.from(keyHex, 'hex');
|
|
29
|
+
}
|
|
30
|
+
// Key is invalid, log warning and regenerate
|
|
31
|
+
console.warn(`[encryption] Invalid key file at ${keyPath}, regenerating...`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Generate a new key
|
|
35
|
+
const key = crypto.randomBytes(KEY_LENGTH);
|
|
36
|
+
mkdirSync(keyDir, { recursive: true });
|
|
37
|
+
writeFileSync(keyPath, key.toString('hex'), { mode: 0o600 });
|
|
38
|
+
return key;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Lazily initialize the key so it's only loaded when first needed
|
|
42
|
+
let _key = null;
|
|
43
|
+
|
|
44
|
+
function getKey() {
|
|
45
|
+
if (!_key) {
|
|
46
|
+
_key = getEncryptionKey();
|
|
47
|
+
}
|
|
48
|
+
return _key;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Encrypt a plaintext string using AES-256-GCM.
|
|
53
|
+
* Returns a string in the format: <iv_hex>:<authTag_hex>:<ciphertext_hex>
|
|
54
|
+
* Returns the original value unchanged if it is null, undefined, or empty.
|
|
55
|
+
* @param {string|null|undefined} plaintext
|
|
56
|
+
* @returns {string|null|undefined}
|
|
57
|
+
*/
|
|
58
|
+
export function encrypt(plaintext) {
|
|
59
|
+
if (plaintext == null || plaintext === '') return plaintext;
|
|
60
|
+
|
|
61
|
+
const key = getKey();
|
|
62
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
63
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
64
|
+
|
|
65
|
+
const encrypted = Buffer.concat([
|
|
66
|
+
cipher.update(plaintext, 'utf-8'),
|
|
67
|
+
cipher.final(),
|
|
68
|
+
]);
|
|
69
|
+
const authTag = cipher.getAuthTag();
|
|
70
|
+
|
|
71
|
+
return [iv.toString('hex'), authTag.toString('hex'), encrypted.toString('hex')].join(SEPARATOR);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Decrypt a ciphertext string that was encrypted with `encrypt()`.
|
|
76
|
+
* Gracefully handles plaintext values (legacy data not yet encrypted) by returning them as-is.
|
|
77
|
+
* Returns the original value unchanged if it is null, undefined, or empty.
|
|
78
|
+
* @param {string|null|undefined} ciphertext
|
|
79
|
+
* @returns {string|null|undefined}
|
|
80
|
+
*/
|
|
81
|
+
export function decrypt(ciphertext) {
|
|
82
|
+
if (ciphertext == null || ciphertext === '') return ciphertext;
|
|
83
|
+
|
|
84
|
+
// If the value doesn't look like our format, treat it as plaintext (legacy)
|
|
85
|
+
const parts = ciphertext.split(SEPARATOR);
|
|
86
|
+
if (parts.length !== PARTS_COUNT) return ciphertext;
|
|
87
|
+
|
|
88
|
+
// Validate hex format and expected lengths
|
|
89
|
+
// IV should be 24 hex chars (12 bytes), auth tag should be 32 hex chars (16 bytes)
|
|
90
|
+
const ivHex = parts[0];
|
|
91
|
+
const authTagHex = parts[1];
|
|
92
|
+
|
|
93
|
+
if (!/^[0-9a-fA-F]{24}$/.test(ivHex) || !/^[0-9a-fA-F]{32}$/.test(authTagHex)) {
|
|
94
|
+
return ciphertext; // Legacy plaintext
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const key = getKey();
|
|
99
|
+
const iv = Buffer.from(parts[0], 'hex');
|
|
100
|
+
const authTag = Buffer.from(parts[1], 'hex');
|
|
101
|
+
const encrypted = Buffer.from(parts[2], 'hex');
|
|
102
|
+
|
|
103
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
104
|
+
decipher.setAuthTag(authTag);
|
|
105
|
+
|
|
106
|
+
return decipher.update(encrypted) + decipher.final('utf-8');
|
|
107
|
+
} catch {
|
|
108
|
+
// Decryption failed — value is likely legacy plaintext, return as-is
|
|
109
|
+
console.warn('[encryption] Failed to decrypt value — returning as-is (possible key mismatch or legacy plaintext)');
|
|
110
|
+
return ciphertext;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Reset the cached encryption key.
|
|
116
|
+
* Only intended for test isolation.
|
|
117
|
+
* @internal
|
|
118
|
+
*/
|
|
119
|
+
export function _resetKeyForTesting() {
|
|
120
|
+
_key = null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Override the key directory so tests use an isolated temp path
|
|
125
|
+
* instead of the real ~/.circuschief/ directory.
|
|
126
|
+
* Pass null to restore the default behaviour.
|
|
127
|
+
* Only intended for test isolation.
|
|
128
|
+
* @param {string|null} dir
|
|
129
|
+
* @internal
|
|
130
|
+
*/
|
|
131
|
+
export function _setKeyDirForTesting(dir) {
|
|
132
|
+
_keyDirOverride = dir;
|
|
133
|
+
_key = null; // also clear the cached key so the new path takes effect
|
|
134
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
|
|
4
|
+
const execAsync = promisify(exec);
|
|
5
|
+
|
|
6
|
+
// Cache gh availability check
|
|
7
|
+
let ghAvailable = null;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Check if gh CLI is installed and authenticated
|
|
11
|
+
* @returns {Promise<boolean>}
|
|
12
|
+
*/
|
|
13
|
+
export async function isGhAvailable() {
|
|
14
|
+
if (ghAvailable !== null) return ghAvailable;
|
|
15
|
+
try {
|
|
16
|
+
await execAsync('gh --version');
|
|
17
|
+
await execAsync('gh auth status');
|
|
18
|
+
ghAvailable = true;
|
|
19
|
+
} catch {
|
|
20
|
+
ghAvailable = false;
|
|
21
|
+
}
|
|
22
|
+
return ghAvailable;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Reset the gh availability cache (useful for testing)
|
|
27
|
+
*/
|
|
28
|
+
export function resetGhAvailableCache() {
|
|
29
|
+
ghAvailable = null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Extract repository information from a PR URL
|
|
34
|
+
* @param {string} prUrl - GitHub PR URL
|
|
35
|
+
* @returns {Object|null} - { owner, repo, number } or null if invalid
|
|
36
|
+
*/
|
|
37
|
+
export function extractPrInfo(prUrl) {
|
|
38
|
+
if (!prUrl) return null;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
// Match GitHub PR URL pattern: https://github.com/{owner}/{repo}/pull/{number}
|
|
42
|
+
const match = prUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)$/);
|
|
43
|
+
if (!match) return null;
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
owner: match[1],
|
|
47
|
+
repo: match[2],
|
|
48
|
+
number: parseInt(match[3], 10),
|
|
49
|
+
};
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Validate that a PR URL belongs to the expected repository
|
|
57
|
+
* @param {string} prUrl - GitHub PR URL
|
|
58
|
+
* @param {string} expectedRepoUrl - Expected repository URL (https://github.com/owner/repo)
|
|
59
|
+
* @returns {Object} - { valid: boolean, mismatch: boolean, error: string|null }
|
|
60
|
+
*/
|
|
61
|
+
export function validatePrRepository(prUrl, expectedRepoUrl) {
|
|
62
|
+
if (!prUrl) {
|
|
63
|
+
return { valid: false, mismatch: false, error: 'No PR URL provided' };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const prInfo = extractPrInfo(prUrl);
|
|
67
|
+
if (!prInfo) {
|
|
68
|
+
return { valid: false, mismatch: false, error: 'Invalid PR URL format' };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// If no expected repo URL, we can't validate - accept it
|
|
72
|
+
if (!expectedRepoUrl) {
|
|
73
|
+
return { valid: true, mismatch: false, error: null };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Extract owner/repo from expected repo URL
|
|
77
|
+
const expectedMatch = expectedRepoUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/?$/);
|
|
78
|
+
if (!expectedMatch) {
|
|
79
|
+
return { valid: true, mismatch: false, error: null };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const expectedOwner = expectedMatch[1];
|
|
83
|
+
const expectedRepo = expectedMatch[2];
|
|
84
|
+
|
|
85
|
+
// Check if PR belongs to expected repository
|
|
86
|
+
if (prInfo.owner !== expectedOwner || prInfo.repo !== expectedRepo) {
|
|
87
|
+
return {
|
|
88
|
+
valid: false,
|
|
89
|
+
mismatch: true,
|
|
90
|
+
error: `PR from ${prInfo.owner}/${prInfo.repo} does not match expected ${expectedOwner}/${expectedRepo}`
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { valid: true, mismatch: false, error: null };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get comprehensive PR info including state, merge conflicts, and CI status
|
|
99
|
+
* @param {string} prUrl - GitHub PR URL
|
|
100
|
+
* @returns {Promise<Object|null>} PR info or null if unavailable
|
|
101
|
+
*/
|
|
102
|
+
export async function getPrInfo(prUrl) {
|
|
103
|
+
if (!(await isGhAvailable())) return null;
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
// First fetch basic PR info (this should always work)
|
|
107
|
+
const { stdout: basicStdout } = await execAsync(
|
|
108
|
+
`gh pr view "${prUrl}" --json state,mergedAt,mergeable,isDraft,title`
|
|
109
|
+
);
|
|
110
|
+
const data = JSON.parse(basicStdout);
|
|
111
|
+
|
|
112
|
+
// Determine PR state
|
|
113
|
+
let state = data.state.toLowerCase();
|
|
114
|
+
if (data.mergedAt) state = 'merged';
|
|
115
|
+
else if (data.isDraft) state = 'draft';
|
|
116
|
+
|
|
117
|
+
// Check for merge conflicts
|
|
118
|
+
// mergeable can be: 'MERGEABLE', 'CONFLICTING', 'UNKNOWN'
|
|
119
|
+
const hasMergeConflicts = data.mergeable === 'CONFLICTING';
|
|
120
|
+
|
|
121
|
+
// Try to fetch CI status separately (may fail due to token permissions)
|
|
122
|
+
let ciStatus = null;
|
|
123
|
+
let ciFailures = [];
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const { stdout: ciStdout } = await execAsync(
|
|
127
|
+
`gh pr view "${prUrl}" --json statusCheckRollup`
|
|
128
|
+
);
|
|
129
|
+
const ciData = JSON.parse(ciStdout);
|
|
130
|
+
const checks = ciData.statusCheckRollup || [];
|
|
131
|
+
|
|
132
|
+
if (checks.length > 0) {
|
|
133
|
+
// Get failed checks with their names
|
|
134
|
+
const failedChecks = checks.filter(
|
|
135
|
+
(c) => c.conclusion === 'FAILURE' || c.conclusion === 'TIMED_OUT'
|
|
136
|
+
);
|
|
137
|
+
ciFailures = failedChecks.map((c) => c.name || c.context || 'Unknown check');
|
|
138
|
+
|
|
139
|
+
// Determine overall CI status
|
|
140
|
+
const hasPending = checks.some(
|
|
141
|
+
(c) => c.status === 'IN_PROGRESS' || c.status === 'QUEUED' || c.status === 'PENDING'
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
if (failedChecks.length > 0) {
|
|
145
|
+
ciStatus = 'failure';
|
|
146
|
+
} else if (hasPending) {
|
|
147
|
+
ciStatus = 'pending';
|
|
148
|
+
} else {
|
|
149
|
+
ciStatus = 'success';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} catch (ciError) {
|
|
153
|
+
// CI status unavailable (likely token permissions) - continue without it
|
|
154
|
+
console.warn('[ghService] CI status unavailable:', ciError.message);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
state,
|
|
159
|
+
merged: Boolean(data.mergedAt),
|
|
160
|
+
hasMergeConflicts,
|
|
161
|
+
ciStatus,
|
|
162
|
+
ciFailures,
|
|
163
|
+
title: data.title || null,
|
|
164
|
+
};
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.warn('[ghService] Failed to get PR info:', error.message);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|