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