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,190 @@
1
+ import { Router } from 'express';
2
+ import { sessions, attachments, sessionSummaries } from '../database.js';
3
+ import { cleanupActiveSession, stopSession, restartSession } from '../services/sessionManager.js';
4
+ import { broadcastToSession, broadcastToProject } from '../websocket.js';
5
+ import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
6
+ import * as gitService from '../services/gitService.js';
7
+ import * as summaryService from '../services/summaryService.js';
8
+ import { executeHookAsync } from '../services/hookService.js';
9
+ import { requireSession, requireSessionAndProject } from '../middleware/sessionLookup.js';
10
+ import { duplicateSession } from '../services/sessionDuplicator.js';
11
+ import { configureSchedule, ScheduleError } from '../services/scheduleService.js';
12
+
13
+ const router = Router();
14
+
15
+ // GET /api/sessions/:id/summary - Get session summary
16
+ router.get('/:id/summary', requireSession, async (req, res) => {
17
+ // Check if generateIfMissing query param is set
18
+ const generateIfMissing = req.query.generate === 'true';
19
+
20
+ try {
21
+ const summary = await summaryService.getSummary(req.params.id, generateIfMissing);
22
+ if (!summary) {
23
+ return res.status(404).json({ error: 'Summary not found' });
24
+ }
25
+ res.json(summary);
26
+ } catch (error) {
27
+ res.status(500).json({ error: error.message });
28
+ }
29
+ });
30
+
31
+ // POST /api/sessions/:id/summary - Generate/regenerate session summary
32
+ router.post('/:id/summary', requireSession, async (req, res) => {
33
+ try {
34
+ const summary = await summaryService.regenerateSummary(req.params.id);
35
+ if (!summary) {
36
+ return res.status(500).json({ error: 'Failed to generate summary' });
37
+ }
38
+ res.status(201).json(summary);
39
+ } catch (error) {
40
+ res.status(500).json({ error: error.message });
41
+ }
42
+ });
43
+
44
+ // PUT /api/sessions/:id/summary - Directly set summary data (for testing/seeding)
45
+ router.put('/:id/summary', requireSession, async (req, res) => {
46
+ try {
47
+ const summary = sessionSummaries.upsert(req.params.id, req.body);
48
+ res.json(summary);
49
+ } catch (error) {
50
+ res.status(500).json({ error: error.message });
51
+ }
52
+ });
53
+
54
+ // POST /api/sessions/:id/schedule - Schedule a follow-up message for an existing session
55
+ router.post('/:id/schedule', requireSessionAndProject, async (req, res) => {
56
+ try {
57
+ const updated = configureSchedule(req.session_, req.body);
58
+ res.json(updated);
59
+ } catch (error) {
60
+ if (error instanceof ScheduleError) {
61
+ return res.status(error.statusCode).json({ error: error.message });
62
+ }
63
+ console.error('Schedule session error:', error);
64
+ res.status(500).json({ error: error.message });
65
+ }
66
+ });
67
+
68
+ // POST /api/sessions/:id/duplicate - Duplicate a session
69
+ router.post('/:id/duplicate', requireSession, async (req, res) => {
70
+ try {
71
+ const { name } = req.body;
72
+ const newSession = await duplicateSession(req.params.id, { name });
73
+
74
+ // Broadcast new session creation to project subscribers
75
+ broadcastToProject(req.session_.projectId, WS_MESSAGE_TYPES.SESSION_CREATED, {
76
+ projectId: req.session_.projectId,
77
+ session: newSession,
78
+ });
79
+
80
+ res.status(201).json(newSession);
81
+ } catch (error) {
82
+ console.error('Error duplicating session:', error);
83
+ res.status(500).json({ error: error.message });
84
+ }
85
+ });
86
+
87
+ // POST /api/sessions/:id/stop - Stop running session
88
+ router.post('/:id/stop', requireSession, async (req, res) => {
89
+ // Allow stopping running, waiting, or stuck sessions (crashed sessions may be stuck in 'running')
90
+ // Don't allow stopping already errored or stopped sessions
91
+ if (req.session_.status === 'error' || req.session_.status === 'stopped') {
92
+ return res.status(400).json({ error: 'Session is not active' });
93
+ }
94
+
95
+ try {
96
+ await stopSession(req.session_.id);
97
+ res.json({ success: true });
98
+ } catch (error) {
99
+ res.status(500).json({ error: error.message });
100
+ }
101
+ });
102
+
103
+ // POST /api/sessions/:id/restart - Restart a completed/error session
104
+ router.post('/:id/restart', requireSession, (req, res) => {
105
+ if (req.session_.status !== 'stopped' && req.session_.status !== 'error') {
106
+ return res.status(400).json({ error: 'Session can only be restarted when stopped or in error state' });
107
+ }
108
+
109
+ try {
110
+ restartSession(req.session_.id);
111
+ res.json({ success: true });
112
+ } catch (error) {
113
+ res.status(500).json({ error: error.message });
114
+ }
115
+ });
116
+
117
+ // DELETE /api/sessions/:id - Delete session
118
+ router.delete('/:id', requireSessionAndProject, async (req, res) => {
119
+ // Collect all descendant session IDs before any deletions
120
+ // (database ON DELETE SET NULL would orphan them otherwise)
121
+ const descendantIds = sessions.getAllDescendantIds(req.params.id);
122
+
123
+ // Helper: clean up and delete a single session by ID
124
+ const cleanupAndDeleteSession = async (sessionId) => {
125
+ // Clean up active session if running
126
+ cleanupActiveSession(sessionId);
127
+
128
+ // Clean up summary service debounce timers
129
+ summaryService.cleanupSession(sessionId);
130
+
131
+ // Clean up attachment files from disk
132
+ try {
133
+ attachments.deleteSessionAttachmentsFromDisk(req.workingDirectory, sessionId);
134
+ } catch (error) {
135
+ console.warn(`Failed to remove attachment files for session ${sessionId}:`, error.message);
136
+ }
137
+
138
+ // Broadcast deletion to close any open WebSocket subscriptions
139
+ broadcastToSession(sessionId, WS_MESSAGE_TYPES.SESSION_DELETED, { sessionId });
140
+
141
+ // Delete session (cascade will handle messages, canvas items, notes)
142
+ sessions.delete(sessionId);
143
+ };
144
+
145
+ // Delete all descendants first (leaves before branches to avoid orphaning)
146
+ for (const descendantId of descendantIds.reverse()) {
147
+ await cleanupAndDeleteSession(descendantId);
148
+ }
149
+
150
+ // Remove git worktree if parent session has one (skip for child sessions - they may share parent's worktree)
151
+ if (req.session_.gitWorktree && !req.session_.parentSessionId) {
152
+ try {
153
+ await gitService.removeWorktree(req.project.workingDirectory, req.session_.gitWorktree, true);
154
+ } catch (error) {
155
+ // Log but don't fail - worktree may already be removed or have issues
156
+ console.warn(`Failed to remove worktree for session ${req.session_.id}:`, error.message);
157
+ }
158
+ }
159
+
160
+ // Clean up and delete the parent session itself
161
+ await cleanupAndDeleteSession(req.params.id);
162
+
163
+ // Broadcast deletion to project subscribers for real-time list updates
164
+ // (sends one event per deleted session so the frontend can remove them all)
165
+ broadcastToProject(req.session_.projectId, WS_MESSAGE_TYPES.SESSION_DELETED, {
166
+ projectId: req.session_.projectId,
167
+ sessionId: req.params.id,
168
+ });
169
+
170
+ for (const descendantId of descendantIds) {
171
+ broadcastToProject(req.session_.projectId, WS_MESSAGE_TYPES.SESSION_DELETED, {
172
+ projectId: req.session_.projectId,
173
+ sessionId: descendantId,
174
+ });
175
+ }
176
+
177
+ // Execute on_session_deleted hook if configured (non-blocking)
178
+ // Skip for child sessions - they share parent's resources and shouldn't trigger teardown
179
+ if (req.project?.onSessionDeleted && !req.session_.parentSessionId) {
180
+ executeHookAsync(req.project.onSessionDeleted, req.workingDirectory, {
181
+ sessionId: req.session_.id,
182
+ projectId: req.project.id,
183
+ sessionName: req.session_.name,
184
+ });
185
+ }
186
+
187
+ res.status(204).send();
188
+ });
189
+
190
+ export default router;
@@ -0,0 +1,141 @@
1
+ import { Router } from 'express';
2
+ import { sessions, messages, todos, conversations, attachments } from '../database.js';
3
+ import { continueSession } from '../services/sessionManager.js';
4
+ import { upload as _upload, handleUploadError } from '../middleware/upload.js';
5
+ import { requireSession, requireSessionAndProject } from '../middleware/sessionLookup.js';
6
+ import * as slashCommandService from '../services/slashCommandService.js';
7
+
8
+ const router = Router();
9
+
10
+ // GET /api/sessions/:id/messages - Get session messages
11
+ // Supports ?conversation_id=xxx to filter by conversation
12
+ router.get('/:id/messages', requireSession, (req, res) => {
13
+ const { conversation_id } = req.query;
14
+
15
+ let sessionMessages;
16
+ let resolvedConvId = null;
17
+ if (conversation_id) {
18
+ // Get messages for specific conversation
19
+ const conv = conversations.getById(conversation_id);
20
+ if (!conv || conv.sessionId !== req.params.id) {
21
+ return res.status(404).json({ error: 'Conversation not found' });
22
+ }
23
+ sessionMessages = messages.getByConversationId(conversation_id);
24
+ resolvedConvId = conversation_id;
25
+ } else {
26
+ // Get messages for active conversation, or all messages if no active conversation
27
+ const activeConv = conversations.getActiveBySessionId(req.params.id);
28
+ if (activeConv) {
29
+ sessionMessages = messages.getByConversationId(activeConv.id);
30
+ resolvedConvId = activeConv.id;
31
+ } else {
32
+ // Fall back to all messages (for legacy/migration)
33
+ sessionMessages = messages.getBySessionId(req.params.id);
34
+ resolvedConvId = 'all (no active conversation)';
35
+ }
36
+ }
37
+
38
+ console.log(`[API] fetchMessages: session ${req.params.id}, conversation ${resolvedConvId}, returned ${sessionMessages.length} messages`);
39
+
40
+ // Attach file attachments to each message (without content for efficiency)
41
+ const messagesWithAttachments = sessionMessages.map((msg) => ({
42
+ ...msg,
43
+ attachments: attachments.getByMessageIdWithoutContent(msg.id),
44
+ }));
45
+
46
+ res.json(messagesWithAttachments);
47
+ });
48
+
49
+ // POST /api/sessions/:id/message - Send follow-up message
50
+ // Supports both JSON and multipart/form-data (for file attachments)
51
+ router.post('/:id/message', _upload.array('files', 10), handleUploadError, requireSessionAndProject, async (req, res) => {
52
+ const content = req.body.content;
53
+ const model = req.body.model || null; // Model to use for this message
54
+ const files = req.files || [];
55
+
56
+ if (!content) {
57
+ return res.status(400).json({ error: 'Content is required' });
58
+ }
59
+
60
+ if (req.session_.status !== 'waiting' && req.session_.status !== 'stopped' && req.session_.status !== 'error') {
61
+ return res.status(400).json({ error: 'Session is not waiting for input' });
62
+ }
63
+
64
+ try {
65
+ // Store file attachments if any - saves to disk in workingDirectory/.attachments
66
+ const messageAttachments = attachments.createBatch(req.session_.id, null, files, req.workingDirectory);
67
+
68
+ // Check if the message is a slash command/skill invocation (starts with "/")
69
+ const resolved = await slashCommandService.resolvePromptSkillOrCommand(
70
+ req.workingDirectory, content, req.project.systemPrompt || null
71
+ );
72
+
73
+ if (resolved) {
74
+ continueSession(req.session_.id, resolved.userMessage, req.workingDirectory, { systemPrompt: resolved.systemPrompt, fileAttachments: messageAttachments, model }).catch((error) => {
75
+ console.error(`Continue session error (${resolved.type}):`, error);
76
+ });
77
+ return res.json({ success: true });
78
+ }
79
+
80
+ // Standard plain text message
81
+ continueSession(req.session_.id, content, req.workingDirectory, { systemPrompt: req.project.systemPrompt, fileAttachments: messageAttachments, model }).catch((error) => {
82
+ console.error('Continue session error:', error);
83
+ });
84
+ res.json({ success: true });
85
+ } catch (error) {
86
+ res.status(500).json({ error: error.message });
87
+ }
88
+ });
89
+
90
+ // GET /api/sessions/:id/todos - Get session todos
91
+ // Supports ?conversation_id=xxx to fetch todos for a specific conversation
92
+ router.get('/:id/todos', requireSession, (req, res) => {
93
+ const { conversation_id } = req.query;
94
+
95
+ let sessionTodos;
96
+ if (conversation_id) {
97
+ // Get todos for specific conversation
98
+ const conv = conversations.getById(conversation_id);
99
+ if (!conv || conv.sessionId !== req.params.id) {
100
+ return res.status(404).json({ error: 'Conversation not found' });
101
+ }
102
+ sessionTodos = todos.getByConversationId(conversation_id);
103
+ } else {
104
+ // Get todos for active conversation, or empty array if no active conversation
105
+ const activeConv = conversations.getActiveBySessionId(req.params.id);
106
+ sessionTodos = activeConv ? todos.getByConversationId(activeConv.id) : [];
107
+ }
108
+
109
+ res.json(sessionTodos);
110
+ });
111
+
112
+ // GET /api/sessions/:id/workflow-latest-response - Get the most recent assistant response across the entire workflow
113
+ router.get('/:id/workflow-latest-response', requireSession, (req, res) => {
114
+ try {
115
+ // Find the root of the workflow
116
+ const rootId = sessions.getRootSessionId(req.params.id);
117
+ if (!rootId) {
118
+ return res.status(404).json({ error: 'Session not found' });
119
+ }
120
+
121
+ // Collect all session IDs in the workflow
122
+ const descendantIds = sessions.getAllDescendantIds(rootId);
123
+ const allSessionIds = [rootId, ...descendantIds];
124
+
125
+ // Find the most recent assistant message across all sessions
126
+ const message = messages.getLatestAssistantMessageForSessions(allSessionIds);
127
+ if (!message) {
128
+ return res.status(404).json({ error: 'No assistant response found' });
129
+ }
130
+
131
+ // Look up the session name for context
132
+ const messageSession = sessions.getById(message.sessionId);
133
+ const sessionName = messageSession?.name || null;
134
+
135
+ res.json({ message, sessionName });
136
+ } catch (error) {
137
+ res.status(500).json({ error: error.message });
138
+ }
139
+ });
140
+
141
+ export default router;
@@ -0,0 +1,51 @@
1
+ import { Router } from 'express';
2
+ import { sessionNotes } from '../database.js';
3
+ import { requireSession } from '../middleware/sessionLookup.js';
4
+
5
+ const router = Router();
6
+
7
+ // GET /api/sessions/:id/notes - Get session notes
8
+ router.get('/:id/notes', requireSession, (req, res) => {
9
+ const notes = sessionNotes.getBySessionId(req.params.id);
10
+ res.json(notes);
11
+ });
12
+
13
+ // POST /api/sessions/:id/notes - Create session note
14
+ router.post('/:id/notes', requireSession, (req, res) => {
15
+ const { content } = req.body;
16
+ if (!content) {
17
+ return res.status(400).json({ error: 'Content is required' });
18
+ }
19
+
20
+ const note = sessionNotes.create(req.params.id, content);
21
+ res.status(201).json(note);
22
+ });
23
+
24
+ // PUT /api/sessions/:id/notes/:noteId - Update session note
25
+ router.put('/:id/notes/:noteId', (req, res) => {
26
+ const note = sessionNotes.getById(req.params.noteId);
27
+ if (!note || note.sessionId !== req.params.id) {
28
+ return res.status(404).json({ error: 'Note not found' });
29
+ }
30
+
31
+ const { content } = req.body;
32
+ if (!content) {
33
+ return res.status(400).json({ error: 'Content is required' });
34
+ }
35
+
36
+ const updated = sessionNotes.update(req.params.noteId, content);
37
+ res.json(updated);
38
+ });
39
+
40
+ // DELETE /api/sessions/:id/notes/:noteId - Delete session note
41
+ router.delete('/:id/notes/:noteId', (req, res) => {
42
+ const note = sessionNotes.getById(req.params.noteId);
43
+ if (!note || note.sessionId !== req.params.id) {
44
+ return res.status(404).json({ error: 'Note not found' });
45
+ }
46
+
47
+ sessionNotes.delete(req.params.noteId);
48
+ res.status(204).send();
49
+ });
50
+
51
+ export default router;
@@ -0,0 +1,252 @@
1
+ import { Router } from 'express';
2
+ import { sessions, sessionTemplates, modelProviders } from '../database.js';
3
+ import { broadcastToSession, broadcastToProject } from '../websocket.js';
4
+ import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
5
+ import * as summaryService from '../services/summaryService.js';
6
+ import { setSessionNameFromPr } from '../services/prUrlService.js';
7
+ import { requireSession } from '../middleware/sessionLookup.js';
8
+
9
+ const router = Router();
10
+
11
+ /**
12
+ * Validate effortLevel field
13
+ * @param {*} value
14
+ * @returns {{ error?: string, value: * }}
15
+ */
16
+ function validateEffortLevel(value) {
17
+ if (value === null) return { value };
18
+ const valid = ['low', 'medium', 'high', 'max', 'auto'];
19
+ if (!valid.includes(value)) {
20
+ return { error: 'Invalid effort level. Must be one of: low, medium, high, max, auto' };
21
+ }
22
+ // Normalize 'auto' to null
23
+ return { value: value === 'auto' ? null : value };
24
+ }
25
+
26
+ /**
27
+ * Validate status field
28
+ * @param {*} value
29
+ * @returns {{ error?: string, value: * }}
30
+ */
31
+ function validateStatus(value) {
32
+ const valid = ['starting', 'running', 'waiting', 'error', 'stopped', 'scheduled'];
33
+ if (!valid.includes(value)) {
34
+ return { error: 'Invalid status' };
35
+ }
36
+ return { value };
37
+ }
38
+
39
+ /**
40
+ * Validate mode field
41
+ * @param {*} value
42
+ * @returns {{ error?: string, value: * }}
43
+ */
44
+ function validateMode(value) {
45
+ const valid = ['plan', 'standard', 'yolo'];
46
+ if (!valid.includes(value)) {
47
+ return { error: 'Invalid mode. Must be one of: plan, standard, yolo' };
48
+ }
49
+ return { value };
50
+ }
51
+
52
+ /**
53
+ * Validate nextTemplateId field
54
+ * @param {*} value
55
+ * @returns {{ error?: string, value: * }}
56
+ */
57
+ function validateNextTemplateId(value) {
58
+ if (value !== null) {
59
+ const template = sessionTemplates.getById(value);
60
+ if (!template) {
61
+ return { error: 'Template not found' };
62
+ }
63
+ }
64
+ return { value };
65
+ }
66
+
67
+ /**
68
+ * Validate providerId field
69
+ * @param {*} value
70
+ * @returns {{ error?: string, value: * }}
71
+ */
72
+ function validateProviderId(value) {
73
+ if (value !== null) {
74
+ const provider = modelProviders.getById(value);
75
+ if (!provider) {
76
+ return { error: 'Provider not found' };
77
+ }
78
+ }
79
+ return { value };
80
+ }
81
+
82
+ /**
83
+ * Validate prUrl field
84
+ * @param {*} value
85
+ * @returns {{ error?: string, value: * }}
86
+ */
87
+ function validatePrUrl(value) {
88
+ if (value === null || value === '') {
89
+ return { value: null };
90
+ }
91
+ if (typeof value !== 'string') {
92
+ return { error: 'prUrl must be a string or null' };
93
+ }
94
+ const prUrlPattern = /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+$/;
95
+ if (!prUrlPattern.test(value)) {
96
+ return { error: 'Invalid PR URL format. Must be a valid GitHub PR URL (e.g., https://github.com/owner/repo/pull/123)' };
97
+ }
98
+ return { value };
99
+ }
100
+
101
+ /**
102
+ * Field definitions for PATCH /:id with optional validators and transforms.
103
+ * Each entry maps a request body field name to its processing config.
104
+ */
105
+ const FIELD_DEFINITIONS = [
106
+ { field: 'name' },
107
+ { field: 'manuallyNamed', transform: Boolean },
108
+ { field: 'thinkingEnabled', transform: Boolean },
109
+ { field: 'effortLevel', validate: validateEffortLevel },
110
+ { field: 'status', validate: validateStatus },
111
+ { field: 'mode', validate: validateMode },
112
+ { field: 'nextTemplateId', validate: validateNextTemplateId },
113
+ { field: 'model' },
114
+ { field: 'pendingModel' },
115
+ { field: 'autoSendPendingPrompt', transform: Boolean },
116
+ { field: 'providerId', validate: validateProviderId },
117
+ { field: 'prUrl', validate: validatePrUrl },
118
+ // Scheduling fields
119
+ { field: 'scheduledAt' },
120
+ { field: 'autoRescheduleEnabled', transform: Boolean },
121
+ { field: 'rescheduleDelayMinutes', transform: (v) => parseInt(v, 10) },
122
+ { field: 'rescheduleOnTokenLimit', transform: Boolean },
123
+ { field: 'rescheduleOnServiceError', transform: Boolean },
124
+ { field: 'maxRescheduleCount', transform: (v) => v ? parseInt(v, 10) : null },
125
+ { field: 'maxTotalTokens', transform: (v) => v ? parseInt(v, 10) : null },
126
+ { field: 'rescheduleCount', transform: (v) => parseInt(v, 10) },
127
+ { field: 'rescheduleAtTokenCount', transform: (v) => v ? parseInt(v, 10) : null },
128
+ ];
129
+
130
+ /**
131
+ * Build update data object from request body using field definitions.
132
+ * Returns { updateData, error } where error is a string if validation failed.
133
+ * @param {object} body - The request body
134
+ * @returns {{ updateData: object, error?: string }}
135
+ */
136
+ function buildUpdateData(body) {
137
+ const updateData = {};
138
+
139
+ for (const { field, validate, transform } of FIELD_DEFINITIONS) {
140
+ const value = body[field];
141
+ if (value === undefined) continue;
142
+
143
+ if (validate) {
144
+ const result = validate(value);
145
+ if (result.error) return { updateData: {}, error: result.error };
146
+ updateData[field] = result.value;
147
+ } else if (transform) {
148
+ updateData[field] = transform(value);
149
+ } else {
150
+ updateData[field] = value;
151
+ }
152
+ }
153
+
154
+ // Special case: auto-set manuallyNamed when name is updated (unless explicitly provided)
155
+ if (body.name !== undefined && body.manuallyNamed === undefined) {
156
+ updateData.manuallyNamed = true;
157
+ }
158
+
159
+ return { updateData };
160
+ }
161
+
162
+ /**
163
+ * Broadcast session update to both session and project subscribers.
164
+ * @param {string} sessionId
165
+ * @param {string} projectId
166
+ * @param {object} updated - The updated session object
167
+ * @param {object} updateData - The fields that were updated
168
+ */
169
+ function broadcastSessionUpdate(sessionId, projectId, updated, updateData) {
170
+ // Broadcast status update if status changed
171
+ if (updateData.status) {
172
+ broadcastToSession(sessionId, WS_MESSAGE_TYPES.SESSION_STATUS, {
173
+ sessionId,
174
+ status: updateData.status,
175
+ });
176
+ }
177
+
178
+ // Broadcast session update to session subscribers (e.g. detail view)
179
+ broadcastToSession(sessionId, WS_MESSAGE_TYPES.SESSION_UPDATED, {
180
+ sessionId,
181
+ session: updated,
182
+ });
183
+
184
+ // Broadcast session update to project subscribers for real-time list updates
185
+ broadcastToProject(projectId, WS_MESSAGE_TYPES.SESSION_UPDATED, {
186
+ projectId,
187
+ sessionId,
188
+ session: updated,
189
+ });
190
+ }
191
+
192
+ // PATCH /api/sessions/:id - Update session settings
193
+ router.patch('/:id', requireSession, (req, res) => {
194
+ const { updateData, error } = buildUpdateData(req.body);
195
+
196
+ if (error) {
197
+ return res.status(400).json({ error });
198
+ }
199
+
200
+ if (Object.keys(updateData).length === 0) {
201
+ return res.status(400).json({ error: 'No valid fields to update' });
202
+ }
203
+
204
+ const updated = sessions.update(req.params.id, updateData);
205
+
206
+ // Propagate PR URL to parent session if set (not when clearing)
207
+ if (updateData.prUrl) {
208
+ summaryService.propagatePrUrlToParent(req.params.id, updateData.prUrl);
209
+
210
+ // Set session name from PR title (fire-and-forget async)
211
+ // The response returns immediately; client gets name update via WebSocket
212
+ setSessionNameFromPr(req.params.id, updateData.prUrl).catch(err => {
213
+ console.error(`[Sessions API] Failed to set session name from PR:`, err);
214
+ });
215
+ }
216
+
217
+ broadcastSessionUpdate(req.params.id, req.session_.projectId, updated, updateData);
218
+
219
+ res.json(updated);
220
+ });
221
+
222
+ // PATCH /api/sessions/:id/pending-prompt - Update pending prompt for auto-save
223
+ router.patch('/:id/pending-prompt', requireSession, (req, res) => {
224
+ const { pendingPrompt } = req.body;
225
+
226
+ // Allow null or string (including empty string for clearing)
227
+ if (pendingPrompt !== null && typeof pendingPrompt !== 'string') {
228
+ return res.status(400).json({ error: 'pendingPrompt must be a string or null' });
229
+ }
230
+
231
+ const updated = sessions.update(req.params.id, { pendingPrompt });
232
+
233
+ // Broadcast update to session subscribers
234
+ broadcastToSession(req.params.id, WS_MESSAGE_TYPES.SESSION_UPDATED, {
235
+ sessionId: req.params.id,
236
+ session: updated,
237
+ });
238
+
239
+ // Broadcast to project subscribers for real-time updates
240
+ broadcastToProject(req.session_.projectId, WS_MESSAGE_TYPES.SESSION_UPDATED, {
241
+ projectId: req.session_.projectId,
242
+ sessionId: req.params.id,
243
+ session: updated,
244
+ });
245
+
246
+ res.json(updated);
247
+ });
248
+
249
+ export default router;
250
+
251
+ // Export for testing
252
+ export { buildUpdateData, broadcastSessionUpdate, FIELD_DEFINITIONS };