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,422 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { platform } from 'os';
|
|
3
|
+
import { commandRuns } from '../database.js';
|
|
4
|
+
import { createRobustEnv } from './nodeSpawnHelper.js';
|
|
5
|
+
import { TerminalOutputProcessor } from './terminalOutput.js';
|
|
6
|
+
|
|
7
|
+
// Re-export for backward compatibility
|
|
8
|
+
export { stripAnsiCodes, TerminalOutputProcessor } from './terminalOutput.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Service for running commands and managing their execution
|
|
12
|
+
*/
|
|
13
|
+
export class CommandRunner {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.processes = new Map(); // runId -> { process, timeout, outputBuffer, lastDbWrite }
|
|
16
|
+
this.outputBufferFlushInterval = 500; // Flush buffered output every 500ms
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create database record for a command run.
|
|
21
|
+
*/
|
|
22
|
+
#createDatabaseRecord(runId, sessionId, buttonId) {
|
|
23
|
+
if (!commandRuns || typeof commandRuns.create !== 'function') return;
|
|
24
|
+
try {
|
|
25
|
+
commandRuns.create({ id: runId, sessionId, buttonId });
|
|
26
|
+
console.log(`[commandRunner.run] Created run record in database for runId: ${runId}`);
|
|
27
|
+
} catch (dbErr) {
|
|
28
|
+
console.warn(`[commandRunner.run] Warning: Failed to create database record for runId: ${runId}`, dbErr.message);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Wrap command with platform-specific TTY allocation.
|
|
34
|
+
*/
|
|
35
|
+
#wrapCommandForPlatform(command) {
|
|
36
|
+
const osType = platform();
|
|
37
|
+
if (osType === 'linux') {
|
|
38
|
+
return `script -q -e -c ${JSON.stringify(command)} /dev/null`;
|
|
39
|
+
}
|
|
40
|
+
return `script -q /dev/null sh -c ${JSON.stringify(command)}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Create process entry with buffer management.
|
|
45
|
+
*/
|
|
46
|
+
#createProcessEntry(child, sessionId, buttonId) {
|
|
47
|
+
return {
|
|
48
|
+
process: child,
|
|
49
|
+
startTime: Date.now(),
|
|
50
|
+
sessionId,
|
|
51
|
+
buttonId,
|
|
52
|
+
output: '',
|
|
53
|
+
outputBuffer: '',
|
|
54
|
+
lastDbWrite: Date.now(),
|
|
55
|
+
bufferFlushTimer: null,
|
|
56
|
+
outputProcessor: new TerminalOutputProcessor(),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Flush buffered output to database.
|
|
62
|
+
*/
|
|
63
|
+
#flushOutputBuffer(entryInput, runId) {
|
|
64
|
+
const entry = entryInput;
|
|
65
|
+
if (!entry.outputBuffer) return;
|
|
66
|
+
if (!entry.sessionId || !entry.buttonId) return;
|
|
67
|
+
if (!commandRuns || typeof commandRuns.appendOutput !== 'function') return;
|
|
68
|
+
try {
|
|
69
|
+
commandRuns.appendOutput(runId, entry.outputBuffer);
|
|
70
|
+
entry.lastDbWrite = Date.now();
|
|
71
|
+
} catch (err) {
|
|
72
|
+
console.warn(`[commandRunner.run] Warning: Error flushing output to database for runId: ${runId}`, err.message);
|
|
73
|
+
}
|
|
74
|
+
entry.outputBuffer = '';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Handle process close event.
|
|
79
|
+
* @param {{ entry: object, runId: string, exitCode: number|null, signal: string|null }} ctx
|
|
80
|
+
* @param {function|undefined} onComplete - Completion callback
|
|
81
|
+
*/
|
|
82
|
+
#handleProcessClose(ctx, onComplete) {
|
|
83
|
+
const { entry, runId, exitCode, signal } = ctx;
|
|
84
|
+
const remainingText = entry.outputProcessor.flush();
|
|
85
|
+
if (remainingText) {
|
|
86
|
+
entry.output += remainingText;
|
|
87
|
+
entry.outputBuffer += remainingText;
|
|
88
|
+
}
|
|
89
|
+
this.#flushOutputBuffer(entry, runId);
|
|
90
|
+
console.log(`[commandRunner.run] Process closed for runId: ${runId}, exitCode: ${exitCode}, signal: ${signal}`);
|
|
91
|
+
|
|
92
|
+
if (commandRuns && typeof commandRuns.complete === 'function' && typeof commandRuns.markKilled === 'function') {
|
|
93
|
+
try {
|
|
94
|
+
if (signal) {
|
|
95
|
+
commandRuns.markKilled(runId, entry.output);
|
|
96
|
+
} else {
|
|
97
|
+
commandRuns.complete(runId, exitCode || 0, entry.output);
|
|
98
|
+
}
|
|
99
|
+
console.log(`[commandRunner.run] Marked run as complete in database for runId: ${runId}`);
|
|
100
|
+
} catch (dbErr) {
|
|
101
|
+
console.warn(`[commandRunner.run] Warning: Error marking run as complete in database for runId: ${runId}`, dbErr.message);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.processes.delete(runId);
|
|
106
|
+
if (onComplete) onComplete(exitCode, entry.output);
|
|
107
|
+
// Use ?? 1 (not signal-specific codes like 143 for SIGTERM) because:
|
|
108
|
+
// - Exit codes >128 indicate signal termination (convention: 128 + signal number)
|
|
109
|
+
// - Normalizing to 1 simplifies error handling for consumers
|
|
110
|
+
// - The signal information is already logged above for debugging
|
|
111
|
+
return exitCode ?? 1;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Run a command and stream output via callback
|
|
116
|
+
*
|
|
117
|
+
* @param {{ runId: string, command: string, workingDirectory: string }} params - Command parameters
|
|
118
|
+
* @param {{ onOutput?: function, onComplete?: function, onError?: function }} callbacks - Callback functions
|
|
119
|
+
* @param {{ sessionId?: string, buttonId?: string }} metadata - Optional metadata
|
|
120
|
+
* @returns {Promise<number>} Exit code
|
|
121
|
+
*/
|
|
122
|
+
async run(params, callbacks = {}, metadata = {}) {
|
|
123
|
+
const { runId, command, workingDirectory } = params;
|
|
124
|
+
const { onOutput, onComplete, onError } = callbacks;
|
|
125
|
+
const { sessionId, buttonId } = metadata;
|
|
126
|
+
|
|
127
|
+
return new Promise((resolve) => {
|
|
128
|
+
try {
|
|
129
|
+
this.#createDatabaseRecord(runId, sessionId, buttonId);
|
|
130
|
+
const wrappedCommand = this.#wrapCommandForPlatform(command);
|
|
131
|
+
|
|
132
|
+
const child = spawn('sh', ['-c', wrappedCommand], {
|
|
133
|
+
cwd: workingDirectory,
|
|
134
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
135
|
+
detached: true,
|
|
136
|
+
env: createRobustEnv(),
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const entry = this.#createProcessEntry(child, sessionId, buttonId);
|
|
140
|
+
this.processes.set(runId, entry);
|
|
141
|
+
|
|
142
|
+
// Buffer timer management
|
|
143
|
+
const clearBufferTimer = () => {
|
|
144
|
+
if (entry.bufferFlushTimer) {
|
|
145
|
+
clearInterval(entry.bufferFlushTimer);
|
|
146
|
+
entry.bufferFlushTimer = null;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
entry.bufferFlushTimer = setInterval(() => this.#flushOutputBuffer(entry, runId), this.outputBufferFlushInterval);
|
|
150
|
+
|
|
151
|
+
// Data handler for both stdout and stderr
|
|
152
|
+
const handleData = (data) => {
|
|
153
|
+
const text = entry.outputProcessor.process(data.toString());
|
|
154
|
+
if (text) {
|
|
155
|
+
entry.output += text;
|
|
156
|
+
entry.outputBuffer += text;
|
|
157
|
+
if (onOutput) onOutput(text);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
child.stdout.on('data', handleData);
|
|
162
|
+
child.stderr.on('data', handleData);
|
|
163
|
+
|
|
164
|
+
child.on('error', (err) => {
|
|
165
|
+
clearBufferTimer();
|
|
166
|
+
const remainingText = entry.outputProcessor.flush();
|
|
167
|
+
if (remainingText) {
|
|
168
|
+
entry.output += remainingText;
|
|
169
|
+
entry.outputBuffer += remainingText;
|
|
170
|
+
}
|
|
171
|
+
this.#flushOutputBuffer(entry, runId);
|
|
172
|
+
const msg = `Failed to execute command: ${err.message}`;
|
|
173
|
+
console.error(`[commandRunner.run] Error for runId: ${runId}`, err);
|
|
174
|
+
if (onError) onError(msg);
|
|
175
|
+
if (commandRuns && typeof commandRuns.complete === 'function') {
|
|
176
|
+
try { commandRuns.complete(runId, 1, entry.output); } catch (dbErr) { console.warn('[commandRunner.run] Warning: Error completing run in database for runId:', runId, dbErr.message); }
|
|
177
|
+
}
|
|
178
|
+
this.processes.delete(runId);
|
|
179
|
+
resolve(1);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
child.on('close', (exitCode, signal) => {
|
|
183
|
+
clearBufferTimer();
|
|
184
|
+
const remainingText = entry.outputProcessor.flush();
|
|
185
|
+
if (remainingText) {
|
|
186
|
+
entry.output += remainingText;
|
|
187
|
+
entry.outputBuffer += remainingText;
|
|
188
|
+
if (onOutput) onOutput(remainingText);
|
|
189
|
+
}
|
|
190
|
+
resolve(this.#handleProcessClose({ entry, runId, exitCode, signal }, onComplete));
|
|
191
|
+
});
|
|
192
|
+
} catch (err) {
|
|
193
|
+
const msg = `Error running command: ${err.message}`;
|
|
194
|
+
console.error(`[commandRunner.run] Exception for runId: ${runId}`, err);
|
|
195
|
+
if (onError) onError(msg);
|
|
196
|
+
// Mark as error in database (if available) and persist the error message
|
|
197
|
+
if (commandRuns && typeof commandRuns.complete === 'function') {
|
|
198
|
+
try {
|
|
199
|
+
commandRuns.complete(runId, 1, `[Error] ${msg}`);
|
|
200
|
+
} catch (dbErr) {
|
|
201
|
+
console.warn(`[commandRunner.run] Warning: Error marking failed run in database for runId: ${runId}`, dbErr.message);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
this.processes.delete(runId);
|
|
205
|
+
resolve(1);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Kill a running process
|
|
212
|
+
* @param {string} runId
|
|
213
|
+
* @returns {boolean} True if process was killed, false if not found
|
|
214
|
+
*/
|
|
215
|
+
kill(runId) {
|
|
216
|
+
const entry = this.processes.get(runId);
|
|
217
|
+
if (!entry) {
|
|
218
|
+
console.log(`[commandRunner.kill] Process not found for runId: ${runId}`);
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
const pid = entry.process.pid;
|
|
224
|
+
console.log(`[commandRunner.kill] Sending SIGTERM to process group for runId: ${runId}, pid: ${pid}`);
|
|
225
|
+
|
|
226
|
+
// Kill the entire process group (negative PID) to ensure child processes are also killed
|
|
227
|
+
try {
|
|
228
|
+
process.kill(-pid, 'SIGTERM');
|
|
229
|
+
} catch (e) {
|
|
230
|
+
// Fallback to killing just the process if process group kill fails
|
|
231
|
+
console.log(`[commandRunner.kill] Process group kill failed, killing single process: ${e.message}`);
|
|
232
|
+
entry.process.kill('SIGTERM');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Give it a moment to terminate gracefully, then force kill
|
|
236
|
+
setTimeout(() => {
|
|
237
|
+
// Check if process is still in our map (not yet closed)
|
|
238
|
+
if (this.processes.has(runId)) {
|
|
239
|
+
console.log(`[commandRunner.kill] Process still running, sending SIGKILL to runId: ${runId}`);
|
|
240
|
+
try {
|
|
241
|
+
process.kill(-pid, 'SIGKILL');
|
|
242
|
+
} catch (e) {
|
|
243
|
+
// Fallback to killing just the process if process group kill fails
|
|
244
|
+
try {
|
|
245
|
+
entry.process.kill('SIGKILL');
|
|
246
|
+
} catch (err) {
|
|
247
|
+
// Process may have already exited
|
|
248
|
+
console.log(`[commandRunner.kill] SIGKILL failed, process may have exited: ${err.message}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
} else {
|
|
252
|
+
console.log(`[commandRunner.kill] Process already exited for runId: ${runId}`);
|
|
253
|
+
}
|
|
254
|
+
}, 1000);
|
|
255
|
+
|
|
256
|
+
// Flush any remaining output (database mark as killed will be done in close event)
|
|
257
|
+
if (entry.bufferFlushTimer) {
|
|
258
|
+
clearInterval(entry.bufferFlushTimer);
|
|
259
|
+
entry.bufferFlushTimer = null;
|
|
260
|
+
}
|
|
261
|
+
if (entry.outputBuffer && commandRuns && typeof commandRuns.appendOutput === 'function') {
|
|
262
|
+
try {
|
|
263
|
+
commandRuns.appendOutput(runId, entry.outputBuffer);
|
|
264
|
+
entry.outputBuffer = '';
|
|
265
|
+
} catch (err) {
|
|
266
|
+
console.warn(
|
|
267
|
+
`[commandRunner.kill] Warning: Error flushing output to database for runId: ${runId}`,
|
|
268
|
+
err.message
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// Note: We don't mark as killed here because the close event will handle it
|
|
273
|
+
// This is to avoid race conditions where we mark it killed before the process actually closes
|
|
274
|
+
|
|
275
|
+
return true;
|
|
276
|
+
} catch (err) {
|
|
277
|
+
console.error(`Error killing process ${runId}:`, err);
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Get all active runs
|
|
284
|
+
* @returns {Map} Map of runId -> process info
|
|
285
|
+
*/
|
|
286
|
+
getActiveRuns() {
|
|
287
|
+
return new Map(this.processes);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Check if a run is active
|
|
292
|
+
* @param {string} runId
|
|
293
|
+
* @returns {boolean}
|
|
294
|
+
*/
|
|
295
|
+
isRunning(runId) {
|
|
296
|
+
return this.processes.has(runId);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Get all running commands for a project
|
|
301
|
+
* Used for merging in-memory running commands with completed runs from the database
|
|
302
|
+
* @param {string} projectId
|
|
303
|
+
* @param {Function} getSessionById - Function to look up session by ID
|
|
304
|
+
* @returns {Array} Running command runs
|
|
305
|
+
*/
|
|
306
|
+
getRunningByProjectId(projectId, getSessionById) {
|
|
307
|
+
const results = [];
|
|
308
|
+
for (const [runId, entry] of this.processes.entries()) {
|
|
309
|
+
// Look up session to check projectId
|
|
310
|
+
const session = getSessionById(entry.sessionId);
|
|
311
|
+
if (session?.projectId === projectId) {
|
|
312
|
+
results.push({
|
|
313
|
+
id: runId,
|
|
314
|
+
runId,
|
|
315
|
+
buttonId: entry.buttonId,
|
|
316
|
+
sessionId: entry.sessionId,
|
|
317
|
+
status: 'running',
|
|
318
|
+
output: entry.output,
|
|
319
|
+
exitCode: null,
|
|
320
|
+
startedAt: entry.startTime,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return results;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Get all active runs for a specific session (both running and recent completed)
|
|
329
|
+
* @param {string} sessionId
|
|
330
|
+
* @returns {Array} Array of run info objects
|
|
331
|
+
*/
|
|
332
|
+
/**
|
|
333
|
+
* Mark an orphaned run as error in the database
|
|
334
|
+
* @param {Object} dbRun - The database run record
|
|
335
|
+
*/
|
|
336
|
+
#markOrphanedRunAsError(dbRun) {
|
|
337
|
+
console.log(
|
|
338
|
+
`[commandRunner.getRunsBySession] Orphaned run detected: ${dbRun.id}, marking as error`
|
|
339
|
+
);
|
|
340
|
+
if (typeof commandRuns.complete === 'function') {
|
|
341
|
+
try {
|
|
342
|
+
commandRuns.complete(dbRun.id, -1, dbRun.output || '');
|
|
343
|
+
} catch (updateErr) {
|
|
344
|
+
console.warn(
|
|
345
|
+
`[commandRunner.getRunsBySession] Failed to update orphaned run: ${updateErr.message}`
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Process a database run record and return a normalized run object
|
|
353
|
+
* @param {Object} dbRun - The database run record
|
|
354
|
+
* @returns {Object} Normalized run object
|
|
355
|
+
*/
|
|
356
|
+
#processDbRun(dbRun) {
|
|
357
|
+
let status = dbRun.status;
|
|
358
|
+
let exitCode = dbRun.exitCode;
|
|
359
|
+
|
|
360
|
+
// If DB shows "running" but we don't have the process in memory,
|
|
361
|
+
// it's an orphaned run (server restarted, process died unexpectedly, etc.)
|
|
362
|
+
if (dbRun.status === 'running' && !this.processes.has(dbRun.id)) {
|
|
363
|
+
this.#markOrphanedRunAsError(dbRun);
|
|
364
|
+
status = 'error';
|
|
365
|
+
exitCode = -1;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
runId: dbRun.id,
|
|
370
|
+
buttonId: dbRun.buttonId,
|
|
371
|
+
status,
|
|
372
|
+
output: dbRun.output,
|
|
373
|
+
exitCode,
|
|
374
|
+
startedAt: dbRun.startedAt,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
getRunsBySession(sessionId) {
|
|
379
|
+
const runs = [];
|
|
380
|
+
|
|
381
|
+
// Add currently running processes
|
|
382
|
+
for (const [runId, entry] of this.processes) {
|
|
383
|
+
if (entry.sessionId === sessionId) {
|
|
384
|
+
runs.push({
|
|
385
|
+
runId,
|
|
386
|
+
buttonId: entry.buttonId,
|
|
387
|
+
status: 'running',
|
|
388
|
+
output: entry.output,
|
|
389
|
+
exitCode: undefined,
|
|
390
|
+
startedAt: entry.startTime,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Add latest completed runs from database (one per button, no time limit)
|
|
396
|
+
this.#appendDbRuns(runs, sessionId);
|
|
397
|
+
|
|
398
|
+
return runs;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
#appendDbRuns(runs, sessionId) {
|
|
402
|
+
// commandRuns might not be available in test environments
|
|
403
|
+
if (!commandRuns || typeof commandRuns.getLatestRunsForSession !== 'function') return;
|
|
404
|
+
try {
|
|
405
|
+
const dbRuns = commandRuns.getLatestRunsForSession(sessionId);
|
|
406
|
+
for (const dbRun of dbRuns) {
|
|
407
|
+
// Don't duplicate running processes
|
|
408
|
+
const isRunningInMemory = runs.some((r) => r.runId === dbRun.id);
|
|
409
|
+
if (isRunningInMemory) continue;
|
|
410
|
+
runs.push(this.#processDbRun(dbRun));
|
|
411
|
+
}
|
|
412
|
+
} catch (err) {
|
|
413
|
+
console.error(
|
|
414
|
+
`[commandRunner.getRunsBySession] Error fetching database runs for sessionId: ${sessionId}`,
|
|
415
|
+
err
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Singleton instance
|
|
422
|
+
export const commandRunner = new CommandRunner();
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { messages } from '../database.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Format an array of messages as a readable transcript.
|
|
5
|
+
* Handles different message types and truncates long content.
|
|
6
|
+
* @param {Array} messageArray - Array of message objects with role and content
|
|
7
|
+
* @returns {string} Formatted transcript
|
|
8
|
+
*/
|
|
9
|
+
export function formatConversationHistory(messageArray) {
|
|
10
|
+
return messageArray.map(msg => {
|
|
11
|
+
const role = msg.role === 'user' ? 'User' : 'Assistant';
|
|
12
|
+
// Truncate very long messages to avoid context overflow
|
|
13
|
+
const content = msg.content.length > 10000
|
|
14
|
+
? `${msg.content.substring(0, 10000) }\n[... message truncated ...]`
|
|
15
|
+
: msg.content;
|
|
16
|
+
return `${role}: ${content}`;
|
|
17
|
+
}).join('\n\n');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Build a context string from previous conversation messages.
|
|
22
|
+
* Used when switching models mid-conversation to maintain context without resuming.
|
|
23
|
+
* @param {string} conversationId - The conversation ID
|
|
24
|
+
* @returns {string} Formatted conversation history as context, or empty string if no messages
|
|
25
|
+
*/
|
|
26
|
+
export function buildConversationContextForModelSwitch(conversationId) {
|
|
27
|
+
const conversationMessages = messages.getByConversationId(conversationId);
|
|
28
|
+
|
|
29
|
+
// Don't include the last user message (that's the current prompt)
|
|
30
|
+
const previousMessages = conversationMessages.slice(0, -1);
|
|
31
|
+
|
|
32
|
+
if (previousMessages.length === 0) {
|
|
33
|
+
return '';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const transcript = formatConversationHistory(previousMessages);
|
|
37
|
+
|
|
38
|
+
return `<conversation_history>
|
|
39
|
+
The following is the conversation history from this session. You switched to a different model mid-conversation, so you're seeing this context to maintain continuity. Continue naturally from where the conversation left off.
|
|
40
|
+
|
|
41
|
+
${transcript}
|
|
42
|
+
</conversation_history>
|
|
43
|
+
|
|
44
|
+
`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Build a context string from previous conversation messages for branched conversations.
|
|
49
|
+
* Used when a conversation is branched and has no claudeSessionId (can't resume).
|
|
50
|
+
* @param {string} conversationId - The conversation ID
|
|
51
|
+
* @returns {string} Formatted conversation history as context, or empty string if no messages
|
|
52
|
+
*/
|
|
53
|
+
export function buildConversationContextForBranch(conversationId) {
|
|
54
|
+
const conversationMessages = messages.getByConversationId(conversationId);
|
|
55
|
+
|
|
56
|
+
// Don't include the last user message (that's the current prompt)
|
|
57
|
+
const previousMessages = conversationMessages.slice(0, -1);
|
|
58
|
+
|
|
59
|
+
if (previousMessages.length === 0) {
|
|
60
|
+
return '';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const transcript = formatConversationHistory(previousMessages);
|
|
64
|
+
|
|
65
|
+
return `<conversation_history>
|
|
66
|
+
The following is the conversation history from this branched session. This is a continuation of a previous conversation. Continue naturally from where the conversation left off, taking into account the full context of what was discussed before.
|
|
67
|
+
|
|
68
|
+
${transcript}
|
|
69
|
+
</conversation_history>
|
|
70
|
+
|
|
71
|
+
`;
|
|
72
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import * as gitService from './gitService.js';
|
|
2
|
+
import { readFile } from 'fs/promises';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
|
|
5
|
+
const MAX_FILE_SIZE = 100 * 1024; // 100KB
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Check if content is binary (contains null bytes)
|
|
9
|
+
* @param {Buffer} buffer
|
|
10
|
+
* @returns {boolean}
|
|
11
|
+
*/
|
|
12
|
+
function isBinary(buffer) {
|
|
13
|
+
// Check first 8KB for null bytes (common binary indicator)
|
|
14
|
+
const checkLength = Math.min(buffer.length, 8192);
|
|
15
|
+
for (let i = 0; i < checkLength; i++) {
|
|
16
|
+
if (buffer[i] === 0) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Generate a synthetic unified diff for a new file
|
|
25
|
+
* @param {string} filePath - The file path (relative)
|
|
26
|
+
* @param {string} content - The file content
|
|
27
|
+
* @returns {string}
|
|
28
|
+
*/
|
|
29
|
+
function generateNewFileDiff(filePath, content) {
|
|
30
|
+
const diffLines = [
|
|
31
|
+
`diff --git a/${filePath} b/${filePath}`,
|
|
32
|
+
'new file mode 100644',
|
|
33
|
+
'--- /dev/null',
|
|
34
|
+
`+++ b/${filePath}`,
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// Handle empty file
|
|
38
|
+
if (content === '') {
|
|
39
|
+
diffLines.push('@@ -0,0 +0,0 @@');
|
|
40
|
+
return diffLines.join('\n');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const lines = content.split('\n');
|
|
44
|
+
// Handle trailing newline - if content ends with \n, the split creates an empty string at the end
|
|
45
|
+
// which we should not count as a line
|
|
46
|
+
const lineCount = content.endsWith('\n') ? lines.length - 1 : lines.length;
|
|
47
|
+
|
|
48
|
+
diffLines.push(`@@ -0,0 +1,${lineCount} @@`);
|
|
49
|
+
for (let i = 0; i < lineCount; i++) {
|
|
50
|
+
diffLines.push(`+${lines[i]}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return diffLines.join('\n');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Generate a placeholder diff for binary files
|
|
58
|
+
* @param {string} filePath
|
|
59
|
+
* @returns {string}
|
|
60
|
+
*/
|
|
61
|
+
function generateBinaryFileDiff(filePath) {
|
|
62
|
+
return [
|
|
63
|
+
`diff --git a/${filePath} b/${filePath}`,
|
|
64
|
+
'new file mode 100644',
|
|
65
|
+
`Binary files /dev/null and b/${filePath} differ`,
|
|
66
|
+
].join('\n');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Generate a placeholder diff for files that are too large
|
|
71
|
+
* @param {string} filePath
|
|
72
|
+
* @param {number} size - File size in bytes
|
|
73
|
+
* @returns {string}
|
|
74
|
+
*/
|
|
75
|
+
function generateTooLargeFileDiff(filePath, size) {
|
|
76
|
+
const sizeKB = Math.round(size / 1024);
|
|
77
|
+
return [
|
|
78
|
+
`diff --git a/${filePath} b/${filePath}`,
|
|
79
|
+
'new file mode 100644',
|
|
80
|
+
'--- /dev/null',
|
|
81
|
+
`+++ b/${filePath}`,
|
|
82
|
+
'@@ -0,0 +1 @@',
|
|
83
|
+
`+[File too large to preview (${sizeKB} KB)]`,
|
|
84
|
+
].join('\n');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Generate synthetic diffs for untracked files
|
|
89
|
+
* @param {string} directory - The git repository directory
|
|
90
|
+
* @param {string[]} filePaths - Array of relative file paths
|
|
91
|
+
* @returns {Promise<string>} - Combined diff string for all files
|
|
92
|
+
*/
|
|
93
|
+
export async function generateUntrackedDiffs(directory, filePaths) {
|
|
94
|
+
if (!filePaths || filePaths.length === 0) {
|
|
95
|
+
return '';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const diffs = await Promise.all(
|
|
99
|
+
filePaths.map(async (filePath) => {
|
|
100
|
+
try {
|
|
101
|
+
const fullPath = join(directory, filePath);
|
|
102
|
+
const buffer = await readFile(fullPath);
|
|
103
|
+
|
|
104
|
+
// Check file size
|
|
105
|
+
if (buffer.length > MAX_FILE_SIZE) {
|
|
106
|
+
return generateTooLargeFileDiff(filePath, buffer.length);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Check if binary
|
|
110
|
+
if (isBinary(buffer)) {
|
|
111
|
+
return generateBinaryFileDiff(filePath);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Generate text diff
|
|
115
|
+
const content = buffer.toString('utf8');
|
|
116
|
+
return generateNewFileDiff(filePath, content);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
// File couldn't be read - generate a placeholder
|
|
119
|
+
return [
|
|
120
|
+
`diff --git a/${filePath} b/${filePath}`,
|
|
121
|
+
'new file mode 100644',
|
|
122
|
+
'--- /dev/null',
|
|
123
|
+
`+++ b/${filePath}`,
|
|
124
|
+
'@@ -0,0 +1 @@',
|
|
125
|
+
`+[Error reading file: ${err.message}]`,
|
|
126
|
+
].join('\n');
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
return diffs.join('\n');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get the combined diff (staged + unstaged + untracked) for a directory
|
|
136
|
+
* @param {string} directory
|
|
137
|
+
* @returns {Promise<{staged: string, unstaged: string, untracked: string}>}
|
|
138
|
+
*/
|
|
139
|
+
export async function getChanges(directory) {
|
|
140
|
+
const [staged, unstaged, untrackedPaths] = await Promise.all([
|
|
141
|
+
gitService.getStagedDiff(directory),
|
|
142
|
+
gitService.getDiff(directory),
|
|
143
|
+
gitService.getUntrackedFiles(directory),
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
// Generate synthetic diffs for untracked files
|
|
147
|
+
const untracked = await generateUntrackedDiffs(directory, untrackedPaths);
|
|
148
|
+
|
|
149
|
+
return { staged, unstaged, untracked };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get changes compared to a specific branch
|
|
154
|
+
* Returns committed changes vs the target branch, plus any local uncommitted changes
|
|
155
|
+
* @param {string} directory - The git repository directory
|
|
156
|
+
* @param {string} branch - Branch to compare against (e.g., 'origin/main')
|
|
157
|
+
* @returns {Promise<{branchDiff: string, staged: string, unstaged: string, untracked: string}>}
|
|
158
|
+
*/
|
|
159
|
+
export async function getChangesBranch(directory, branch) {
|
|
160
|
+
const [branchDiff, staged, unstaged, untrackedPaths] = await Promise.all([
|
|
161
|
+
gitService.getDiffBetweenRefs(directory, branch, 'HEAD'), // Committed changes vs branch
|
|
162
|
+
gitService.getStagedDiff(directory), // Actual staged changes (local)
|
|
163
|
+
gitService.getDiff(directory), // Actual unstaged changes (local)
|
|
164
|
+
gitService.getUntrackedFiles(directory),
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
// Generate synthetic diffs for untracked files
|
|
168
|
+
const untracked = await generateUntrackedDiffs(directory, untrackedPaths);
|
|
169
|
+
|
|
170
|
+
return { branchDiff, staged, unstaged, untracked };
|
|
171
|
+
}
|
|
172
|
+
|