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,281 @@
1
+ import os from 'os';
2
+ import fs from 'fs';
3
+ import { execFile, execFileSync } from 'child_process';
4
+ import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
5
+ import { broadcast } from '../ws/index.js';
6
+
7
+ // Broadcast interval in milliseconds (5 seconds)
8
+ const BROADCAST_INTERVAL_MS = 5000;
9
+
10
+ // Interval handle
11
+ let intervalId = null;
12
+
13
+ // Previous CPU sample for delta computation
14
+ let prevCpus = null;
15
+
16
+ /**
17
+ * Compute CPU usage percentage from two cpu samples.
18
+ * @param {os.CpuInfo[]} prevCpus
19
+ * @param {os.CpuInfo[]} currCpus
20
+ * @returns {number} CPU usage percent (0-100)
21
+ */
22
+ export function computeCpuUsage(prevCpuSamples, currCpuSamples) {
23
+ if (!prevCpuSamples || !currCpuSamples || prevCpuSamples.length !== currCpuSamples.length) {
24
+ return 0;
25
+ }
26
+
27
+ let totalIdle = 0;
28
+ let totalTick = 0;
29
+
30
+ for (let i = 0; i < currCpuSamples.length; i++) {
31
+ const prev = prevCpuSamples[i].times;
32
+ const curr = currCpuSamples[i].times;
33
+
34
+ const idleDelta = curr.idle - prev.idle;
35
+ const userDelta = curr.user - prev.user;
36
+ const niceDelta = curr.nice - prev.nice;
37
+ const sysDelta = curr.sys - prev.sys;
38
+ const irqDelta = curr.irq - prev.irq;
39
+
40
+ const tickDelta = idleDelta + userDelta + niceDelta + sysDelta + irqDelta;
41
+
42
+ totalIdle += idleDelta;
43
+ totalTick += tickDelta;
44
+ }
45
+
46
+ if (totalTick === 0) return 0;
47
+
48
+ const usagePercent = ((totalTick - totalIdle) / totalTick) * 100;
49
+ return Math.round(usagePercent * 10) / 10;
50
+ }
51
+
52
+ /**
53
+ * Get available memory bytes using platform-specific methods.
54
+ *
55
+ * os.freemem() only reports truly "free" memory, ignoring reclaimable
56
+ * caches and buffers. On macOS especially, this makes memory appear nearly
57
+ * exhausted even when the system is not under pressure.
58
+ *
59
+ * - macOS: uses vm_stat to sum free + purgeable + speculative pages
60
+ * - Linux: reads MemAvailable from /proc/meminfo
61
+ * - Fallback: os.freemem()
62
+ *
63
+ * @returns {number} Available memory in bytes
64
+ */
65
+ export function getAvailableMemoryBytes() {
66
+ const platform = os.platform();
67
+
68
+ if (platform === 'darwin') {
69
+ try {
70
+ const stdout = execFileSync('vm_stat', { encoding: 'utf8', timeout: 3000 });
71
+ return parseVmStatOutput(stdout);
72
+ } catch {
73
+ return os.freemem();
74
+ }
75
+ }
76
+
77
+ if (platform === 'linux') {
78
+ try {
79
+ const content = fs.readFileSync('/proc/meminfo', 'utf8');
80
+ return parseMemInfoOutput(content);
81
+ } catch {
82
+ return os.freemem();
83
+ }
84
+ }
85
+
86
+ return os.freemem();
87
+ }
88
+
89
+ /**
90
+ * Parse macOS vm_stat output to compute available memory bytes.
91
+ *
92
+ * macOS categorises physical pages as:
93
+ * - free: truly unused
94
+ * - inactive: not recently accessed, reclaimable immediately
95
+ * - speculative: speculatively allocated, reclaimable
96
+ * - purgeable: marked purgeable by apps, reclaimable
97
+ * - active: recently accessed by running processes
98
+ * - wired: locked by the kernel, cannot be paged out
99
+ * - compressed: compressed in the compressor
100
+ *
101
+ * "Available" = free + inactive + purgeable + speculative
102
+ * This matches what macOS considers available (not under pressure).
103
+ *
104
+ * @param {string} stdout - Output from vm_stat command
105
+ * @returns {number} Available memory in bytes
106
+ */
107
+ export function parseVmStatOutput(stdout) {
108
+ if (!stdout || typeof stdout !== 'string') return os.freemem();
109
+
110
+ // First line contains page size, e.g. "Mach Virtual Memory Statistics: (page size of 16384 bytes)"
111
+ const pageSizeMatch = stdout.match(/page size of (\d+) bytes/);
112
+ const pageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : 16384;
113
+
114
+ // Parse page counts - vm_stat format: "Pages free: 123456."
115
+ const getValue = (label) => {
116
+ const match = stdout.match(new RegExp(`${label}:\\s+(\\d+)`));
117
+ return match ? parseInt(match[1], 10) : 0;
118
+ };
119
+
120
+ const free = getValue('Pages free');
121
+ const inactive = getValue('Pages inactive');
122
+ const purgeable = getValue('Pages purgeable');
123
+ const speculative = getValue('Pages speculative');
124
+
125
+ const availableBytes = (free + inactive + purgeable + speculative) * pageSize;
126
+
127
+ // Sanity check: if result is 0 or unreasonably small, fall back
128
+ if (availableBytes <= 0) return os.freemem();
129
+
130
+ return availableBytes;
131
+ }
132
+
133
+ /**
134
+ * Parse Linux /proc/meminfo to get MemAvailable.
135
+ *
136
+ * @param {string} content - Contents of /proc/meminfo
137
+ * @returns {number} Available memory in bytes
138
+ */
139
+ export function parseMemInfoOutput(content) {
140
+ if (!content || typeof content !== 'string') return os.freemem();
141
+
142
+ const match = content.match(/MemAvailable:\s+(\d+)\s+kB/);
143
+ if (!match) return os.freemem();
144
+
145
+ return parseInt(match[1], 10) * 1024;
146
+ }
147
+
148
+ /**
149
+ * Compute memory metrics from OS.
150
+ * @param {number} [totalBytes] - Total memory bytes (defaults to os.totalmem()); injectable for testing
151
+ * @param {number} [availableBytes] - Available memory bytes (defaults to platform-aware available memory); injectable for testing
152
+ * @returns {{ usedPercent: number, usedGB: number, totalGB: number }}
153
+ */
154
+ export function computeMemoryMetrics(totalBytes = os.totalmem(), availableBytes = getAvailableMemoryBytes()) {
155
+ const usedBytes = totalBytes - availableBytes;
156
+
157
+ const totalGB = Math.round((totalBytes / (1024 ** 3)) * 10) / 10;
158
+ const usedGB = Math.round((usedBytes / (1024 ** 3)) * 10) / 10;
159
+ const usedPercent = Math.round((usedBytes / totalBytes) * 1000) / 10;
160
+
161
+ return { usedPercent, usedGB, totalGB };
162
+ }
163
+
164
+ /**
165
+ * Parse `df -k /` output to extract disk metrics.
166
+ * @param {string} stdout
167
+ * @returns {{ usedPercent: number, freeGB: number, totalGB: number } | null}
168
+ */
169
+ export function parseDfOutput(stdout) {
170
+ if (!stdout || typeof stdout !== 'string') return null;
171
+
172
+ const lines = stdout.trim().split('\n');
173
+ // df output: header line + data line(s)
174
+ // We want the second line (index 1)
175
+ if (lines.length < 2) return null;
176
+
177
+ const dataLine = lines[1].trim();
178
+ if (!dataLine) return null;
179
+
180
+ // df -k columns: Filesystem 1K-blocks Used Available Use% Mounted-on
181
+ const parts = dataLine.split(/\s+/);
182
+ if (parts.length < 5) return null;
183
+
184
+ const totalKB = parseInt(parts[1], 10);
185
+ const usedKB = parseInt(parts[2], 10);
186
+ const availKB = parseInt(parts[3], 10);
187
+
188
+ if (isNaN(totalKB) || isNaN(usedKB) || isNaN(availKB) || totalKB === 0) return null;
189
+
190
+ const totalGB = Math.round((totalKB / (1024 ** 2)) * 10) / 10;
191
+ const freeGB = Math.round((availKB / (1024 ** 2)) * 10) / 10;
192
+ // Use (total - available) instead of "used" column for usedPercent.
193
+ // On macOS APFS, the Used column only reports one volume snapshot, not total usage.
194
+ // On Linux, Used + Available ≠ Total due to reserved blocks.
195
+ const effectiveUsedKB = totalKB - availKB;
196
+ const usedPercent = Math.round((effectiveUsedKB / totalKB) * 1000) / 10;
197
+
198
+ return { usedPercent, freeGB, totalGB };
199
+ }
200
+
201
+ /**
202
+ * Collect disk metrics using df command.
203
+ * Returns null if df is unavailable or fails.
204
+ * @returns {Promise<{ usedPercent: number, freeGB: number, totalGB: number } | null>}
205
+ */
206
+ async function collectDiskMetrics() {
207
+ return new Promise((resolve) => {
208
+ const controller = new AbortController();
209
+ const timeout = setTimeout(() => controller.abort(), 3000);
210
+
211
+ execFile('df', ['-k', '/'], { signal: controller.signal }, (error, stdout) => {
212
+ clearTimeout(timeout);
213
+ if (error) {
214
+ resolve(null);
215
+ return;
216
+ }
217
+ resolve(parseDfOutput(stdout));
218
+ });
219
+ });
220
+ }
221
+
222
+ /**
223
+ * Collect and broadcast system metrics.
224
+ */
225
+ async function collectAndBroadcast() {
226
+ // CPU: sample current, compute delta from previous
227
+ const currCpus = os.cpus();
228
+ const cpuUsagePercent = computeCpuUsage(prevCpus, currCpus);
229
+ prevCpus = currCpus;
230
+
231
+ // Memory
232
+ const memory = computeMemoryMetrics();
233
+
234
+ // Disk (may be null on failure or Windows)
235
+ const disk = await collectDiskMetrics();
236
+
237
+ const payload = {
238
+ cpu: {
239
+ usagePercent: cpuUsagePercent,
240
+ coreCount: currCpus.length,
241
+ model: currCpus[0]?.model || 'Unknown',
242
+ },
243
+ memory,
244
+ disk,
245
+ };
246
+
247
+ broadcast(WS_MESSAGE_TYPES.SYSTEM_METRICS, payload);
248
+ }
249
+
250
+ /**
251
+ * Start the system monitor service.
252
+ * Broadcasts system metrics every 5 seconds.
253
+ */
254
+ export function start() {
255
+ if (intervalId) {
256
+ console.log('[SystemMonitor] Already running');
257
+ return;
258
+ }
259
+
260
+ console.log('[SystemMonitor] Starting system metrics broadcast');
261
+
262
+ // Take initial CPU sample before first interval fires
263
+ prevCpus = os.cpus();
264
+
265
+ intervalId = setInterval(collectAndBroadcast, BROADCAST_INTERVAL_MS);
266
+ }
267
+
268
+ /**
269
+ * Stop the system monitor service.
270
+ */
271
+ export function stop() {
272
+ if (intervalId) {
273
+ clearInterval(intervalId);
274
+ intervalId = null;
275
+ prevCpus = null;
276
+ console.log('[SystemMonitor] Stopped system metrics broadcast');
277
+ }
278
+ }
279
+
280
+ // Export for testing
281
+ export { BROADCAST_INTERVAL_MS, collectAndBroadcast };
@@ -0,0 +1,197 @@
1
+ import { Liquid } from 'liquidjs';
2
+ import { sessions, sessionTemplates, sessionSummaries, projects } from '../database.js';
3
+ import { setupGitForSession } from './gitSessionSetup.js';
4
+ import { runSession } from './sessionManager.js';
5
+ import { broadcastToProject } from '../websocket.js';
6
+ import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
7
+
8
+ const liquid = new Liquid();
9
+
10
+ /**
11
+ * Get the root session (the original session that started a template chain)
12
+ * @param {Object} session - The current session
13
+ * @returns {Object} The root session (or the current session if it has no parent)
14
+ */
15
+ export function getRootSession(session) {
16
+ let current = session;
17
+ while (current.parentSessionId) {
18
+ const parent = sessions.getById(current.parentSessionId);
19
+ if (!parent) break;
20
+ current = parent;
21
+ }
22
+ return current;
23
+ }
24
+
25
+ /**
26
+ * Build a template context object for a session and its summary.
27
+ */
28
+ function buildSessionContext(session, summary) {
29
+ return {
30
+ id: session.id,
31
+ name: session.name,
32
+ status: session.status,
33
+ summary: summary?.fullSummary || summary?.shortSummary || 'No summary available',
34
+ shortSummary: summary?.shortSummary || 'No summary available',
35
+ fullSummary: summary?.fullSummary || 'No summary available',
36
+ keyActions: summary?.keyActions || [],
37
+ filesModified: summary?.filesModified || [],
38
+ outcome: summary?.outcome || session.status,
39
+ };
40
+ }
41
+
42
+ /**
43
+ * Render a template prompt with parent session and root session context
44
+ * @param {string} templatePrompt - The Liquid template string
45
+ * @param {{ parentSession: Object, parentSummary: Object|null, rootSession: Object, rootSummary: Object|null }} sessionContext - Session context objects
46
+ * @returns {Promise<string>} The rendered prompt
47
+ */
48
+ export async function renderTemplatePrompt(templatePrompt, sessionContext) {
49
+ const { parentSession, parentSummary, rootSession, rootSummary } = sessionContext;
50
+ const context = {
51
+ parentSession: buildSessionContext(parentSession, parentSummary),
52
+ rootSession: buildSessionContext(rootSession, rootSummary),
53
+ };
54
+
55
+ return liquid.parseAndRender(templatePrompt, context);
56
+ }
57
+
58
+ /**
59
+ * Derive session settings from template and root session.
60
+ * Template values take precedence if set, otherwise inherit from root.
61
+ */
62
+ function deriveSessionSettings(template, rootSession) {
63
+ return {
64
+ thinkingEnabled: template.thinkingEnabled !== null ? template.thinkingEnabled : rootSession.thinkingEnabled,
65
+ gitBranch: template.gitBranch || rootSession.gitBranch,
66
+ gitMode: template.gitMode || null,
67
+ model: template.model !== null ? template.model : rootSession.model,
68
+ mode: template.mode !== null ? template.mode : rootSession.mode,
69
+ effortLevel: template.effortLevel !== null ? template.effortLevel : rootSession.effortLevel,
70
+ // Inherit rescheduling settings from root session
71
+ autoRescheduleEnabled: rootSession.autoRescheduleEnabled,
72
+ rescheduleOnTokenLimit: rootSession.rescheduleOnTokenLimit,
73
+ rescheduleOnServiceError: rootSession.rescheduleOnServiceError,
74
+ rescheduleDelayMinutes: rootSession.rescheduleDelayMinutes,
75
+ rescheduleAtTokenCount: rootSession.rescheduleAtTokenCount,
76
+ maxRescheduleCount: rootSession.maxRescheduleCount,
77
+ maxTotalTokens: rootSession.maxTotalTokens,
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Resolve the working directory for a new template-triggered session.
83
+ * Inherits the parent worktree if available, otherwise sets up git normally.
84
+ * @param {Object} parentSession - The parent session
85
+ * @param {Object} project - The project
86
+ * @param {Object} settings - Derived session settings
87
+ * @param {string} newSessionId - The new session ID
88
+ * @returns {Promise<{workingDirectory: string, gitWorktree: string|null}>}
89
+ */
90
+ async function resolveWorkingDirectory(parentSession, project, settings, newSessionId) {
91
+ if (parentSession.gitWorktree) {
92
+ console.log(`Template trigger: Inheriting parent worktree: ${parentSession.gitWorktree}`);
93
+ return { workingDirectory: parentSession.gitWorktree, gitWorktree: parentSession.gitWorktree };
94
+ }
95
+
96
+ const gitSetup = await setupGitForSession({
97
+ projectDir: project.workingDirectory,
98
+ gitMode: settings.gitMode,
99
+ gitBranch: settings.gitBranch,
100
+ sessionId: newSessionId,
101
+ });
102
+ return { workingDirectory: gitSetup.workingDirectory, gitWorktree: gitSetup.gitWorktree };
103
+ }
104
+
105
+ /**
106
+ * Check if a session should trigger its next template and do so
107
+ * Called when a session completes (status changes to completed, error, or stopped)
108
+ * @param {string} sessionId - The session that just completed
109
+ */
110
+ export async function checkAndTriggerNextTemplate(sessionId) {
111
+ const session = sessions.getById(sessionId);
112
+ if (!session) {
113
+ console.warn(`Template trigger: Session ${sessionId} not found`);
114
+ return;
115
+ }
116
+
117
+ if (!session.nextTemplateId) {
118
+ return;
119
+ }
120
+
121
+ const template = sessionTemplates.getById(session.nextTemplateId);
122
+ if (!template) {
123
+ console.warn(`Template trigger: Template ${session.nextTemplateId} not found for session ${sessionId}`);
124
+ return;
125
+ }
126
+
127
+ const project = projects.getById(session.projectId);
128
+ if (!project) {
129
+ console.warn(`Template trigger: Project ${session.projectId} not found for session ${sessionId}`);
130
+ return;
131
+ }
132
+
133
+ console.log(`Template trigger: Triggering template "${template.name}" after session "${session.name}"`);
134
+
135
+ try {
136
+ const parentSummary = sessionSummaries.getBySessionId(sessionId);
137
+ const rootSession = getRootSession(session);
138
+ const rootSummary = sessionSummaries.getBySessionId(rootSession.id);
139
+
140
+ const renderedPrompt = await renderTemplatePrompt(template.prompt, { parentSession: session, parentSummary, rootSession, rootSummary });
141
+ const settings = deriveSessionSettings(template, rootSession);
142
+ const newSessionName = `${template.name} (from: ${session.name})`;
143
+
144
+ const newSession = sessions.create(
145
+ session.projectId,
146
+ newSessionName,
147
+ renderedPrompt,
148
+ {
149
+ mode: settings.mode,
150
+ thinkingEnabled: settings.thinkingEnabled,
151
+ gitBranch: settings.gitBranch,
152
+ parentSessionId: null,
153
+ status: 'starting',
154
+ model: settings.model,
155
+ effortLevel: settings.effortLevel,
156
+ }
157
+ );
158
+
159
+ sessions.update(newSession.id, {
160
+ parentSessionId: session.id,
161
+ nextTemplateId: template.nextTemplateId || null,
162
+ autoRescheduleEnabled: settings.autoRescheduleEnabled,
163
+ rescheduleOnTokenLimit: settings.rescheduleOnTokenLimit,
164
+ rescheduleOnServiceError: settings.rescheduleOnServiceError,
165
+ rescheduleDelayMinutes: settings.rescheduleDelayMinutes,
166
+ rescheduleAtTokenCount: settings.rescheduleAtTokenCount,
167
+ maxRescheduleCount: settings.maxRescheduleCount,
168
+ maxTotalTokens: settings.maxTotalTokens,
169
+ });
170
+
171
+ const { workingDirectory, gitWorktree } = await resolveWorkingDirectory(session, project, settings, newSession.id);
172
+
173
+ if (gitWorktree) {
174
+ sessions.update(newSession.id, { gitWorktree });
175
+ }
176
+
177
+ const updatedSession = sessions.getById(newSession.id);
178
+ broadcastToProject(session.projectId, WS_MESSAGE_TYPES.SESSION_CREATED, {
179
+ projectId: session.projectId,
180
+ session: updatedSession,
181
+ });
182
+
183
+ runSession(newSession.id, renderedPrompt, workingDirectory, { systemPrompt: project.systemPrompt, model: settings.model }).catch((error) => {
184
+ console.error(`Template trigger: Error running session ${newSession.id}:`, error);
185
+ const errorSession = sessions.update(newSession.id, { status: 'error', error: error.message });
186
+ broadcastToProject(session.projectId, WS_MESSAGE_TYPES.SESSION_UPDATED, {
187
+ projectId: session.projectId,
188
+ sessionId: newSession.id,
189
+ session: errorSession,
190
+ });
191
+ });
192
+
193
+ console.log(`Template trigger: Created and started session ${newSession.id}`);
194
+ } catch (error) {
195
+ console.error(`Template trigger: Failed to trigger template for session ${sessionId}:`, error);
196
+ }
197
+ }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Strip ANSI escape codes from text
3
+ * Removes all CSI (Control Sequence Introducer) sequences:
4
+ * - SGR codes: \x1b[...m (colors, bold, italic, etc.)
5
+ * - Cursor movement: \x1b[1A, \x1b[2B, etc.
6
+ * - Line/screen clearing: \x1b[2K, \x1b[0J, etc.
7
+ * - Other CSI sequences: \x1b[...H, \x1b[...J, etc.
8
+ *
9
+ * @param {string} text - Text potentially containing ANSI codes
10
+ * @returns {string} Text with all ANSI codes removed
11
+ */
12
+ export function stripAnsiCodes(text) {
13
+ if (!text || typeof text !== 'string') {
14
+ return text;
15
+ }
16
+ // Match all CSI sequences: ESC [ <params> <final-char>
17
+ // This covers colors, cursor movement, line clearing, and other terminal control sequences
18
+ // eslint-disable-next-line no-control-regex
19
+ return text.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '');
20
+ }
21
+
22
+ /**
23
+ * Terminal output processor that simulates cursor control behavior
24
+ *
25
+ * When tools like yarn run in TTY mode, they use cursor control sequences to create
26
+ * animated progress displays. For example:
27
+ * \x1b[2K\x1b[1G[] 0/576 <- clear line, go to column 1, print progress
28
+ * \x1b[2K\x1b[1G[] 136/576 <- clear line, go to column 1, print NEW progress
29
+ *
30
+ * On a real terminal, the second line overwrites the first. But when we just strip
31
+ * the codes, we get "[] 0/576[] 136/576" concatenated together.
32
+ *
33
+ * This processor simulates the terminal behavior by:
34
+ * 1. Maintaining a "current line" buffer (content since last newline)
35
+ * 2. When we see \x1b[2K (clear line), we clear the current line buffer
36
+ * 3. When we see \x1b[1G (cursor to column 1), we clear the current line buffer
37
+ * 4. When we see \r (carriage return), we clear the current line buffer
38
+ * 5. When we see \n, we flush the current line and start fresh
39
+ * 6. All ANSI escape codes are stripped from output
40
+ */
41
+ export class TerminalOutputProcessor {
42
+ constructor() {
43
+ /** @type {string} Content on the current line (since last newline) */
44
+ this.currentLine = '';
45
+ }
46
+
47
+ /**
48
+ * Handle a CSI (Control Sequence Introducer) command
49
+ * Returns true if the current line should be cleared
50
+ * @param {string} cmd - The command character
51
+ * @param {string} params - The parameters string
52
+ * @returns {boolean} Whether to clear the current line
53
+ */
54
+ #shouldClearLineForCSI(cmd, params) {
55
+ // Erase in Line: [0K = to end, [1K = to start, [2K = entire line
56
+ if (cmd === 'K') return true;
57
+ // Cursor Character Absolute: [1G = go to column 1 (start of line)
58
+ if (cmd === 'G' && (params === '' || params === '1')) return true;
59
+ // Cursor Position: [n;mH or [n;mf moves cursor to row n, column m
60
+ if (cmd === 'H' || cmd === 'f') return true;
61
+ // Cursor movement: A=up, B=down, C=right, D=left
62
+ if (cmd === 'A' || cmd === 'B' || cmd === 'C' || cmd === 'D') return true;
63
+ // Erase in Display: [0J = to end, [1J = to start, [2J = entire screen
64
+ if (cmd === 'J') return true;
65
+ return false;
66
+ }
67
+
68
+ /**
69
+ * Try to parse and handle a CSI escape sequence starting at position i.
70
+ * Returns the new index past the sequence, or -1 if not a complete CSI sequence.
71
+ * @param {string} chunk
72
+ * @param {number} i - current index (pointing at ESC)
73
+ * @returns {number} new index past the sequence, or -1
74
+ */
75
+ #handleCSISequence(chunk, i) {
76
+ if (chunk[i] !== '\x1b' || chunk[i + 1] !== '[') return -1;
77
+ // Parse the CSI sequence: ESC [ <params> <command>
78
+ let j = i + 2;
79
+ while (j < chunk.length && /[0-9;?]/.test(chunk[j])) {
80
+ j++;
81
+ }
82
+ if (j >= chunk.length) return -1;
83
+
84
+ const cmd = chunk[j];
85
+ const params = chunk.slice(i + 2, j);
86
+
87
+ // Handle cursor control sequences that affect line content
88
+ if (this.#shouldClearLineForCSI(cmd, params)) this.currentLine = '';
89
+ // All other sequences (including color codes 'm') are just stripped
90
+
91
+ return j + 1;
92
+ }
93
+
94
+ /**
95
+ * Process a chunk of terminal output, simulating cursor control behavior
96
+ *
97
+ * @param {string} chunk - Raw terminal output chunk
98
+ * @returns {string} Processed output with cursor behavior simulated and ANSI codes stripped
99
+ */
100
+ process(chunk) {
101
+ if (!chunk || typeof chunk !== 'string') {
102
+ return '';
103
+ }
104
+
105
+ let output = '';
106
+ let i = 0;
107
+
108
+ while (i < chunk.length) {
109
+ // Check for ESC sequence
110
+ const csiEnd = this.#handleCSISequence(chunk, i);
111
+ if (csiEnd >= 0) {
112
+ i = csiEnd;
113
+ continue;
114
+ }
115
+
116
+ // Handle carriage return - go to start of line (used for overwriting)
117
+ if (chunk[i] === '\r') {
118
+ // Don't clear if next char is \n (normal line ending)
119
+ if (chunk[i + 1] !== '\n') {
120
+ this.currentLine = '';
121
+ }
122
+ i++;
123
+ continue;
124
+ }
125
+
126
+ // Handle newline - flush current line and start fresh
127
+ if (chunk[i] === '\n') {
128
+ output += `${this.currentLine }\n`;
129
+ this.currentLine = '';
130
+ i++;
131
+ continue;
132
+ }
133
+
134
+ // Regular character - add to current line
135
+ this.currentLine += chunk[i];
136
+ i++;
137
+ }
138
+
139
+ return output;
140
+ }
141
+
142
+ /**
143
+ * Flush any remaining content in the current line buffer
144
+ * Call this when the stream ends to get the final incomplete line
145
+ *
146
+ * @returns {string} Any remaining content
147
+ */
148
+ flush() {
149
+ const remaining = this.currentLine;
150
+ this.currentLine = '';
151
+ return remaining;
152
+ }
153
+
154
+ /**
155
+ * Reset the processor state
156
+ */
157
+ reset() {
158
+ this.currentLine = '';
159
+ }
160
+ }
@@ -0,0 +1,58 @@
1
+ import { todos, conversations } from '../database.js';
2
+ import { broadcastToSession } from '../websocket.js';
3
+ import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
4
+
5
+ /**
6
+ * Update todos for a conversation (replaces all existing todos for that conversation)
7
+ * Called when Claude executes TodoWrite
8
+ * @param {string} sessionId
9
+ * @param {string} conversationId
10
+ * @param {Array<{content: string, status: string}>} todoList
11
+ * @returns {Array}
12
+ */
13
+ export function updateTodos(sessionId, conversationId, todoList) {
14
+ const updatedTodos = todos.replaceAllForConversation(sessionId, conversationId, todoList);
15
+ broadcastToSession(sessionId, WS_MESSAGE_TYPES.TODOS_UPDATE, {
16
+ sessionId,
17
+ conversationId,
18
+ todos: updatedTodos,
19
+ });
20
+ return updatedTodos;
21
+ }
22
+
23
+ /**
24
+ * Get all todos for a conversation
25
+ * @param {string} conversationId
26
+ * @returns {Array}
27
+ */
28
+ export function getTodosByConversation(conversationId) {
29
+ return todos.getByConversationId(conversationId);
30
+ }
31
+
32
+ /**
33
+ * Get all todos for the active conversation of a session
34
+ * Falls back to empty array if no active conversation
35
+ * @param {string} sessionId
36
+ * @returns {Array}
37
+ */
38
+ export function getTodosForSession(sessionId) {
39
+ const activeConv = conversations.getActiveBySessionId(sessionId);
40
+ if (activeConv) {
41
+ return todos.getByConversationId(activeConv.id);
42
+ }
43
+ return [];
44
+ }
45
+
46
+ /**
47
+ * Clear all todos for a conversation
48
+ * @param {string} sessionId
49
+ * @param {string} conversationId
50
+ */
51
+ export function clearTodos(sessionId, conversationId) {
52
+ todos.deleteByConversationId(conversationId);
53
+ broadcastToSession(sessionId, WS_MESSAGE_TYPES.TODOS_UPDATE, {
54
+ sessionId,
55
+ conversationId,
56
+ todos: [],
57
+ });
58
+ }