circuschief 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/package.json +33 -0
  2. package/packages/server/bin/cli.js +4 -0
  3. package/packages/server/src/agents/AgentGateway.js +64 -0
  4. package/packages/server/src/agents/BaseAgent.js +41 -0
  5. package/packages/server/src/agents/LoggingAgentWrapper.js +73 -0
  6. package/packages/server/src/agents/adapters/ClaudeCodeAdapter.js +33 -0
  7. package/packages/server/src/agents/adapters/CodexAdapter.js +26 -0
  8. package/packages/server/src/agents/types.js +43 -0
  9. package/packages/server/src/agents/vcr/CassetteStore.js +111 -0
  10. package/packages/server/src/agents/vcr/VCRAgentAdapter.js +126 -0
  11. package/packages/server/src/agents/vcr/VCRSummaryWrapper.js +71 -0
  12. package/packages/server/src/api/canvas-helpers.js +249 -0
  13. package/packages/server/src/api/canvas-trash-routes.js +205 -0
  14. package/packages/server/src/api/canvas.js +331 -0
  15. package/packages/server/src/api/commandButtons.js +312 -0
  16. package/packages/server/src/api/commands.js +169 -0
  17. package/packages/server/src/api/filesystem.js +62 -0
  18. package/packages/server/src/api/git.js +85 -0
  19. package/packages/server/src/api/index.js +44 -0
  20. package/packages/server/src/api/kanban.js +342 -0
  21. package/packages/server/src/api/metrics.js +194 -0
  22. package/packages/server/src/api/projects-helpers.js +43 -0
  23. package/packages/server/src/api/projects-session-helpers.js +295 -0
  24. package/packages/server/src/api/projects.js +384 -0
  25. package/packages/server/src/api/providers.js +249 -0
  26. package/packages/server/src/api/quickResponses.js +129 -0
  27. package/packages/server/src/api/sessions-archive.js +69 -0
  28. package/packages/server/src/api/sessions-commands.js +220 -0
  29. package/packages/server/src/api/sessions-conversations.js +168 -0
  30. package/packages/server/src/api/sessions-draft.js +72 -0
  31. package/packages/server/src/api/sessions-lifecycle.js +190 -0
  32. package/packages/server/src/api/sessions-messages.js +141 -0
  33. package/packages/server/src/api/sessions-notes.js +51 -0
  34. package/packages/server/src/api/sessions-patch.js +252 -0
  35. package/packages/server/src/api/sessions-streaming.js +86 -0
  36. package/packages/server/src/api/sessions.js +269 -0
  37. package/packages/server/src/api/settings.js +194 -0
  38. package/packages/server/src/api/templates.js +63 -0
  39. package/packages/server/src/app.js +51 -0
  40. package/packages/server/src/database.js +58 -0
  41. package/packages/server/src/db/AgentCallLogRepository.js +322 -0
  42. package/packages/server/src/db/AttachmentRepository.js +191 -0
  43. package/packages/server/src/db/BaseRepository.js +39 -0
  44. package/packages/server/src/db/CanvasItemRepository.js +315 -0
  45. package/packages/server/src/db/CommandButtonRepository.js +75 -0
  46. package/packages/server/src/db/CommandRunRepository.js +219 -0
  47. package/packages/server/src/db/ConversationRepository.js +379 -0
  48. package/packages/server/src/db/DatabaseManager.js +91 -0
  49. package/packages/server/src/db/KanbanBoardRepository.js +92 -0
  50. package/packages/server/src/db/KanbanCardRepository.js +286 -0
  51. package/packages/server/src/db/KanbanLaneRepository.js +279 -0
  52. package/packages/server/src/db/MessageRepository.js +156 -0
  53. package/packages/server/src/db/ProjectDefaultsRepository.js +173 -0
  54. package/packages/server/src/db/ProjectRepository.js +110 -0
  55. package/packages/server/src/db/ProviderRepository.js +307 -0
  56. package/packages/server/src/db/QuickResponseRepository.js +186 -0
  57. package/packages/server/src/db/SessionNoteRepository.js +60 -0
  58. package/packages/server/src/db/SessionRepository.js +314 -0
  59. package/packages/server/src/db/SessionSummaryRepository.js +200 -0
  60. package/packages/server/src/db/SessionTemplateRepository.js +171 -0
  61. package/packages/server/src/db/SettingsRepository.js +211 -0
  62. package/packages/server/src/db/TodoRepository.js +132 -0
  63. package/packages/server/src/db/WorkLogRepository.js +122 -0
  64. package/packages/server/src/db/conversation-helpers.js +119 -0
  65. package/packages/server/src/db/index.js +100 -0
  66. package/packages/server/src/db/migrations/canvasItemsMigrations.js +109 -0
  67. package/packages/server/src/db/migrations/conversationsMigrations.js +183 -0
  68. package/packages/server/src/db/migrations/index.js +199 -0
  69. package/packages/server/src/db/migrations/kanbanMigrations.js +99 -0
  70. package/packages/server/src/db/migrations/migrationUtils.js +55 -0
  71. package/packages/server/src/db/migrations/miscMigrations.js +242 -0
  72. package/packages/server/src/db/migrations/projectsMigrations.js +95 -0
  73. package/packages/server/src/db/migrations/sessionsMigrations.js +282 -0
  74. package/packages/server/src/db/session-helpers.js +150 -0
  75. package/packages/server/src/index.js +106 -0
  76. package/packages/server/src/logger.js +22 -0
  77. package/packages/server/src/middleware/sessionLookup.js +57 -0
  78. package/packages/server/src/middleware/upload.js +94 -0
  79. package/packages/server/src/schema.sql +363 -0
  80. package/packages/server/src/services/agentCallLogger.js +116 -0
  81. package/packages/server/src/services/canvasStore.js +56 -0
  82. package/packages/server/src/services/childSessionContext.js +61 -0
  83. package/packages/server/src/services/commandRunner.js +422 -0
  84. package/packages/server/src/services/conversationContext.js +72 -0
  85. package/packages/server/src/services/diffService.js +172 -0
  86. package/packages/server/src/services/draftSessionService.js +181 -0
  87. package/packages/server/src/services/encryption.js +134 -0
  88. package/packages/server/src/services/ghService.js +169 -0
  89. package/packages/server/src/services/gitService.js +520 -0
  90. package/packages/server/src/services/gitSessionSetup.js +48 -0
  91. package/packages/server/src/services/hookService.js +60 -0
  92. package/packages/server/src/services/kanbanService.js +262 -0
  93. package/packages/server/src/services/kanbanTriggers.js +273 -0
  94. package/packages/server/src/services/nodeSpawnHelper.js +63 -0
  95. package/packages/server/src/services/prStatusService.js +204 -0
  96. package/packages/server/src/services/prUrlService.js +224 -0
  97. package/packages/server/src/services/providerTestService.js +81 -0
  98. package/packages/server/src/services/scheduleService.js +110 -0
  99. package/packages/server/src/services/schedulerService.js +281 -0
  100. package/packages/server/src/services/sessionDuplicator.js +63 -0
  101. package/packages/server/src/services/sessionErrors.js +173 -0
  102. package/packages/server/src/services/sessionExecution.js +378 -0
  103. package/packages/server/src/services/sessionManager.js +356 -0
  104. package/packages/server/src/services/sessionPrompts.js +427 -0
  105. package/packages/server/src/services/sessionProvider.js +107 -0
  106. package/packages/server/src/services/slashCommandDiscovery.js +258 -0
  107. package/packages/server/src/services/slashCommandPluginDiscovery.js +216 -0
  108. package/packages/server/src/services/slashCommandService.js +306 -0
  109. package/packages/server/src/services/streamEventCallbacks.js +170 -0
  110. package/packages/server/src/services/streamEventHandler.js +488 -0
  111. package/packages/server/src/services/streamUsageHandler.js +228 -0
  112. package/packages/server/src/services/summaryBroadcast.js +61 -0
  113. package/packages/server/src/services/summaryClaudeClient.js +180 -0
  114. package/packages/server/src/services/summaryPrompts.js +169 -0
  115. package/packages/server/src/services/summaryService.js +552 -0
  116. package/packages/server/src/services/summaryStaleCheck.js +35 -0
  117. package/packages/server/src/services/systemMonitor.js +281 -0
  118. package/packages/server/src/services/templateTriggerService.js +197 -0
  119. package/packages/server/src/services/terminalOutput.js +160 -0
  120. package/packages/server/src/services/todoStore.js +58 -0
  121. package/packages/server/src/services/usageTracker.js +69 -0
  122. package/packages/server/src/services/withConcurrencyGuard.js +110 -0
  123. package/packages/server/src/websocket.js +10 -0
  124. package/packages/server/src/ws/WebSocketManager.js +240 -0
  125. package/packages/server/src/ws/index.js +50 -0
  126. package/packages/shared/package.json +27 -0
  127. package/packages/shared/src/constants.js +44 -0
  128. package/packages/shared/src/contracts/canvas.js +25 -0
  129. package/packages/shared/src/contracts/commandButtons.js +36 -0
  130. package/packages/shared/src/contracts/kanban.js +142 -0
  131. package/packages/shared/src/contracts/projects.js +63 -0
  132. package/packages/shared/src/contracts/providers.js +81 -0
  133. package/packages/shared/src/contracts/quickResponses.js +44 -0
  134. package/packages/shared/src/contracts/sessions.js +112 -0
  135. package/packages/shared/src/contracts/templates.js +51 -0
  136. package/packages/shared/src/index.js +5 -0
  137. package/packages/shared/src/protocol.js +76 -0
  138. package/packages/shared/src/routeParams.js +36 -0
  139. package/packages/shared/src/types.js +167 -0
  140. package/packages/shared/src/utils.js +101 -0
  141. package/packages/web/dist/assets/ActiveSessionsView-BQc76Jc8.js +1 -0
  142. package/packages/web/dist/assets/ActiveSessionsView-ofSvx-K1.css +1 -0
  143. package/packages/web/dist/assets/AgentLogsView-CTCjHjsu.js +2 -0
  144. package/packages/web/dist/assets/AgentLogsView-D90PnQVk.css +1 -0
  145. package/packages/web/dist/assets/ApiClient-Dbs1H78V.js +1 -0
  146. package/packages/web/dist/assets/ArchiveConfirmModal-CCxSZ52u.js +1 -0
  147. package/packages/web/dist/assets/ArchiveConfirmModal-CQZeuYBz.css +1 -0
  148. package/packages/web/dist/assets/CommandButtonDetailView-CF_-LXpU.js +1 -0
  149. package/packages/web/dist/assets/CommandButtonDetailView-DBm3rzhw.css +1 -0
  150. package/packages/web/dist/assets/EffortLevelSelector-BQaQmU2d.css +1 -0
  151. package/packages/web/dist/assets/EffortLevelSelector-DPofLvm-.js +1 -0
  152. package/packages/web/dist/assets/GeneralSettingsView-BCf53fpC.css +1 -0
  153. package/packages/web/dist/assets/GeneralSettingsView-BY1G-Kv8.js +1 -0
  154. package/packages/web/dist/assets/InterpolationHelp-CgdbNcJB.js +1 -0
  155. package/packages/web/dist/assets/InterpolationHelp-iNxTxmhs.css +1 -0
  156. package/packages/web/dist/assets/MarkdownEditor-CqT1U8lo.js +2 -0
  157. package/packages/web/dist/assets/MarkdownEditor-enuH2yvP.css +1 -0
  158. package/packages/web/dist/assets/ModelSelector-BBn_Ve0D.js +1 -0
  159. package/packages/web/dist/assets/ModelSelector-DPPD-92R.css +1 -0
  160. package/packages/web/dist/assets/NewSessionView-Bo5l49nu.js +3 -0
  161. package/packages/web/dist/assets/NewSessionView-Byoi1XdQ.css +1 -0
  162. package/packages/web/dist/assets/PathChooser-BoMGzeg2.css +1 -0
  163. package/packages/web/dist/assets/PathChooser-Cx9gQ-Qt.js +1 -0
  164. package/packages/web/dist/assets/ProjectEditView-BFuscj-V.js +1 -0
  165. package/packages/web/dist/assets/ProjectEditView-DNwBUNRk.css +1 -0
  166. package/packages/web/dist/assets/ProjectListView-C55H1JHQ.css +1 -0
  167. package/packages/web/dist/assets/ProjectListView-Dj0jBZ46.js +1 -0
  168. package/packages/web/dist/assets/ProjectNewView-Brdp-xUu.js +1 -0
  169. package/packages/web/dist/assets/ProjectNewView-CpgE4R-l.css +1 -0
  170. package/packages/web/dist/assets/ProvidersView-B_QQF3RM.css +1 -0
  171. package/packages/web/dist/assets/ProvidersView-Cxc-1skq.js +1 -0
  172. package/packages/web/dist/assets/QuickResponseSettings-B2eVAtHW.js +1 -0
  173. package/packages/web/dist/assets/QuickResponseSettings-B8188A1D.css +1 -0
  174. package/packages/web/dist/assets/QuickResponsesPanel-DIBQFj0W.css +1 -0
  175. package/packages/web/dist/assets/QuickResponsesPanel-lU8pW2B0.js +1 -0
  176. package/packages/web/dist/assets/ResizableTextarea-B5nAA0RV.css +1 -0
  177. package/packages/web/dist/assets/ResizableTextarea-DSy1mWGY.js +1 -0
  178. package/packages/web/dist/assets/SessionCard-BvjLwVYg.js +1 -0
  179. package/packages/web/dist/assets/SessionCard-D20G3bX8.css +1 -0
  180. package/packages/web/dist/assets/SessionDetailView-BQbPg-RJ.js +36 -0
  181. package/packages/web/dist/assets/SessionDetailView-BrMG4p2-.css +1 -0
  182. package/packages/web/dist/assets/SessionFormOptions-BgqFR-5f.js +1 -0
  183. package/packages/web/dist/assets/SessionFormOptions-BuLlDF-7.css +1 -0
  184. package/packages/web/dist/assets/SessionListView-BAIBtJF7.css +1 -0
  185. package/packages/web/dist/assets/SessionListView-CYIHI8qF.js +1 -0
  186. package/packages/web/dist/assets/SessionLogStream-B-FwUMJQ.js +18 -0
  187. package/packages/web/dist/assets/SessionLogStream-zPUTiGbe.css +1 -0
  188. package/packages/web/dist/assets/SettingsView-DC8-hTQ-.css +1 -0
  189. package/packages/web/dist/assets/SettingsView-fZxpiGp7.js +1 -0
  190. package/packages/web/dist/assets/SlashCommandWizard-BB30cSvo.css +1 -0
  191. package/packages/web/dist/assets/SlashCommandWizard-BgaOw9W3.js +1 -0
  192. package/packages/web/dist/assets/SummarySettingsView-DcsmSVJI.css +1 -0
  193. package/packages/web/dist/assets/SummarySettingsView-eeu1Xq86.js +1 -0
  194. package/packages/web/dist/assets/TemplateDetailView-DEPKSwDo.js +1 -0
  195. package/packages/web/dist/assets/TemplateDetailView-DT2m06W7.css +1 -0
  196. package/packages/web/dist/assets/apl-B4CMkyY2.js +1 -0
  197. package/packages/web/dist/assets/asciiarmor-Df11BRmG.js +1 -0
  198. package/packages/web/dist/assets/asn1-EdZsLKOL.js +1 -0
  199. package/packages/web/dist/assets/asterisk-B-8jnY81.js +1 -0
  200. package/packages/web/dist/assets/brainfuck-C4LP7Hcl.js +1 -0
  201. package/packages/web/dist/assets/clike-B9uivgTg.js +1 -0
  202. package/packages/web/dist/assets/clojure-BMjYHr_A.js +1 -0
  203. package/packages/web/dist/assets/cmake-BQqOBYOt.js +1 -0
  204. package/packages/web/dist/assets/cobol-CWcv1MsR.js +1 -0
  205. package/packages/web/dist/assets/coffeescript-S37ZYGWr.js +1 -0
  206. package/packages/web/dist/assets/commandButtons-DNSHH8IA.js +4 -0
  207. package/packages/web/dist/assets/commonlisp-DBKNyK5s.js +1 -0
  208. package/packages/web/dist/assets/crystal-SjHAIU92.js +1 -0
  209. package/packages/web/dist/assets/css-BnMrqG3P.js +1 -0
  210. package/packages/web/dist/assets/cypher-C_CwsFkJ.js +1 -0
  211. package/packages/web/dist/assets/d-pRatUO7H.js +1 -0
  212. package/packages/web/dist/assets/diff-DbItnlRl.js +1 -0
  213. package/packages/web/dist/assets/dockerfile-BKs6k2Af.js +1 -0
  214. package/packages/web/dist/assets/dtd-DF_7sFjM.js +1 -0
  215. package/packages/web/dist/assets/dylan-DwRh75JA.js +1 -0
  216. package/packages/web/dist/assets/ebnf-CDyGwa7X.js +1 -0
  217. package/packages/web/dist/assets/ecl-Cabwm37j.js +1 -0
  218. package/packages/web/dist/assets/eiffel-CnydiIhH.js +1 -0
  219. package/packages/web/dist/assets/elm-vLlmbW-K.js +1 -0
  220. package/packages/web/dist/assets/erlang-BNw1qcRV.js +1 -0
  221. package/packages/web/dist/assets/factor-kuTfRLto.js +1 -0
  222. package/packages/web/dist/assets/fcl-Kvtd6kyn.js +1 -0
  223. package/packages/web/dist/assets/forth-Ffai-XNe.js +1 -0
  224. package/packages/web/dist/assets/fortran-DYz_wnZ1.js +1 -0
  225. package/packages/web/dist/assets/gas-Bneqetm1.js +1 -0
  226. package/packages/web/dist/assets/gherkin-heZmZLOM.js +1 -0
  227. package/packages/web/dist/assets/groovy-D9Dt4D0W.js +1 -0
  228. package/packages/web/dist/assets/haskell-BWDZoCOh.js +1 -0
  229. package/packages/web/dist/assets/haxe-H-WmDvRZ.js +1 -0
  230. package/packages/web/dist/assets/http-DBlCnlav.js +1 -0
  231. package/packages/web/dist/assets/idl-BEugSyMb.js +1 -0
  232. package/packages/web/dist/assets/index-BZlHgDSz.js +1 -0
  233. package/packages/web/dist/assets/index-BhWX8AfE.js +2 -0
  234. package/packages/web/dist/assets/index-Bi3XvF_f.js +1 -0
  235. package/packages/web/dist/assets/index-BqXoPf_D.js +1 -0
  236. package/packages/web/dist/assets/index-CAuTOZSD.js +1 -0
  237. package/packages/web/dist/assets/index-CKYk-fkb.js +1 -0
  238. package/packages/web/dist/assets/index-CTumW_tV.js +318 -0
  239. package/packages/web/dist/assets/index-CVOJVSsC.js +82 -0
  240. package/packages/web/dist/assets/index-CXK2Z3_z.js +1 -0
  241. package/packages/web/dist/assets/index-CYllQ3Vd.js +1 -0
  242. package/packages/web/dist/assets/index-CpsfI08O.js +1 -0
  243. package/packages/web/dist/assets/index-DQkhDeTA.js +3 -0
  244. package/packages/web/dist/assets/index-DWP8iCBp.js +1 -0
  245. package/packages/web/dist/assets/index-DkVb9W_J.js +1 -0
  246. package/packages/web/dist/assets/index-DmKHPbIa.js +1 -0
  247. package/packages/web/dist/assets/index-DrlQi03X.js +1 -0
  248. package/packages/web/dist/assets/index-gmCCsCQ1.css +1 -0
  249. package/packages/web/dist/assets/index-prTEzzgO.js +1 -0
  250. package/packages/web/dist/assets/index-wqgejMCM.js +1 -0
  251. package/packages/web/dist/assets/index-yh0ZHIWw.js +7 -0
  252. package/packages/web/dist/assets/javascript-qCveANmP.js +1 -0
  253. package/packages/web/dist/assets/julia-DuME0IfC.js +1 -0
  254. package/packages/web/dist/assets/livescript-BwQOo05w.js +1 -0
  255. package/packages/web/dist/assets/lua-BgMRiT3U.js +1 -0
  256. package/packages/web/dist/assets/mathematica-DTrFuWx2.js +1 -0
  257. package/packages/web/dist/assets/mbox-CNhZ1qSd.js +1 -0
  258. package/packages/web/dist/assets/mirc-CjQqDB4T.js +1 -0
  259. package/packages/web/dist/assets/mllike-CXdrOF99.js +1 -0
  260. package/packages/web/dist/assets/modelica-Dc1JOy9r.js +1 -0
  261. package/packages/web/dist/assets/mscgen-BA5vi2Kp.js +1 -0
  262. package/packages/web/dist/assets/mumps-BT43cFF4.js +1 -0
  263. package/packages/web/dist/assets/nginx-DdIZxoE0.js +1 -0
  264. package/packages/web/dist/assets/nsis-LdVXkNf5.js +1 -0
  265. package/packages/web/dist/assets/ntriples-BfvgReVJ.js +1 -0
  266. package/packages/web/dist/assets/octave-Ck1zUtKM.js +1 -0
  267. package/packages/web/dist/assets/oz-BzwKVEFT.js +1 -0
  268. package/packages/web/dist/assets/pascal--L3eBynH.js +1 -0
  269. package/packages/web/dist/assets/perl-CdXCOZ3F.js +1 -0
  270. package/packages/web/dist/assets/pig-CevX1Tat.js +1 -0
  271. package/packages/web/dist/assets/powershell-CFHJl5sT.js +1 -0
  272. package/packages/web/dist/assets/projects-DbBQQH-V.js +1 -0
  273. package/packages/web/dist/assets/properties-C78fOPTZ.js +1 -0
  274. package/packages/web/dist/assets/protobuf-ChK-085T.js +1 -0
  275. package/packages/web/dist/assets/providers-ceCc4xRU.js +1 -0
  276. package/packages/web/dist/assets/pug-DukmZTjD.js +1 -0
  277. package/packages/web/dist/assets/puppet-DMA9R1ak.js +1 -0
  278. package/packages/web/dist/assets/python-BuPzkPfP.js +1 -0
  279. package/packages/web/dist/assets/q-pXgVlZs6.js +1 -0
  280. package/packages/web/dist/assets/r-DUYO_cvP.js +1 -0
  281. package/packages/web/dist/assets/rpm-CTu-6PCP.js +1 -0
  282. package/packages/web/dist/assets/ruby-B2Rjki9n.js +1 -0
  283. package/packages/web/dist/assets/sas-B4kiWyti.js +1 -0
  284. package/packages/web/dist/assets/scheme-C41bIUwD.js +1 -0
  285. package/packages/web/dist/assets/sessions-D681M81k.js +1 -0
  286. package/packages/web/dist/assets/settings-D0evez2V.js +1 -0
  287. package/packages/web/dist/assets/shell-CjFT_Tl9.js +1 -0
  288. package/packages/web/dist/assets/sieve-C3Gn_uJK.js +1 -0
  289. package/packages/web/dist/assets/simple-mode-GW_nhZxv.js +1 -0
  290. package/packages/web/dist/assets/smalltalk-CnHTOXQT.js +1 -0
  291. package/packages/web/dist/assets/solr-DehyRSwq.js +1 -0
  292. package/packages/web/dist/assets/sparql-DkYu6x3z.js +1 -0
  293. package/packages/web/dist/assets/spreadsheet-BCZA_wO0.js +1 -0
  294. package/packages/web/dist/assets/sql-D0XecflT.js +1 -0
  295. package/packages/web/dist/assets/stex-C3f8Ysf7.js +1 -0
  296. package/packages/web/dist/assets/style-BTin-zR_.css +1 -0
  297. package/packages/web/dist/assets/stylus-B533Al4x.js +1 -0
  298. package/packages/web/dist/assets/swift-BzpIVaGY.js +1 -0
  299. package/packages/web/dist/assets/tcl-DVfN8rqt.js +1 -0
  300. package/packages/web/dist/assets/textile-CnDTJFAw.js +1 -0
  301. package/packages/web/dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
  302. package/packages/web/dist/assets/tiki-DGYXhP31.js +1 -0
  303. package/packages/web/dist/assets/toml-Bm5Em-hy.js +1 -0
  304. package/packages/web/dist/assets/troff-wAsdV37c.js +1 -0
  305. package/packages/web/dist/assets/ttcn-CfJYG6tj.js +1 -0
  306. package/packages/web/dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
  307. package/packages/web/dist/assets/turtle-B1tBg_DP.js +1 -0
  308. package/packages/web/dist/assets/vb-CmGdzxic.js +1 -0
  309. package/packages/web/dist/assets/vbscript-BuJXcnF6.js +1 -0
  310. package/packages/web/dist/assets/velocity-D8B20fx6.js +1 -0
  311. package/packages/web/dist/assets/verilog-C6RDOZhf.js +1 -0
  312. package/packages/web/dist/assets/vhdl-lSbBsy5d.js +1 -0
  313. package/packages/web/dist/assets/webidl-ZXfAyPTL.js +1 -0
  314. package/packages/web/dist/assets/xquery-CQfU5ijd.js +1 -0
  315. package/packages/web/dist/assets/yacas-BJ4BC0dw.js +1 -0
  316. package/packages/web/dist/assets/z80-Hz9HOZM7.js +1 -0
  317. package/packages/web/dist/favicon.png +0 -0
  318. package/packages/web/dist/index.html +17 -0
  319. package/packages/web/dist/logo.png +0 -0
@@ -0,0 +1,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
+