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,314 @@
1
+ import { BaseRepository } from './BaseRepository.js';
2
+ import { databaseManager } from './DatabaseManager.js';
3
+ import { messages, conversations } from './index.js';
4
+ import {
5
+ ACTIVITY_FIELDS_SQL,
6
+ mapTokenUsage,
7
+ mapScheduling,
8
+ parseCreateConfig,
9
+ buildUpdateClauses,
10
+ } from './session-helpers.js';
11
+
12
+ /**
13
+ * Session repository class
14
+ */
15
+ export class SessionRepository extends BaseRepository {
16
+ constructor() {
17
+ super('sessions', SessionRepository.#mapSession);
18
+ }
19
+
20
+ static #mapSession(row) {
21
+ return {
22
+ id: row.id,
23
+ projectId: row.project_id,
24
+ name: row.name,
25
+ status: row.status,
26
+ mode: row.mode,
27
+ model: row.model,
28
+ thinkingEnabled: Boolean(row.thinking_enabled),
29
+ archived: Boolean(row.archived),
30
+ starred: Boolean(row.starred),
31
+ manuallyNamed: Boolean(row.manually_named),
32
+ gitBranch: row.git_branch,
33
+ gitWorktree: row.git_worktree,
34
+ prUrl: row.pr_url,
35
+ error: row.error,
36
+ costUsd: row.cost_usd,
37
+ claudeSessionId: row.claude_session_id,
38
+ nextTemplateId: row.next_template_id,
39
+ parentSessionId: row.parent_session_id,
40
+ pendingPrompt: row.pending_prompt || null,
41
+ pendingModel: row.pending_model || null,
42
+ effortLevel: row.effort_level || null,
43
+ autoSendPendingPrompt: Boolean(row.auto_send_pending_prompt),
44
+ slashCommands: row.slash_commands || null,
45
+ ...mapTokenUsage(row),
46
+ ...mapScheduling(row),
47
+ // Kanban fields
48
+ targetLaneId: row.target_lane_id || null,
49
+ laneTriggerDepth: row.lane_trigger_depth || 0,
50
+ createdAt: row.created_at,
51
+ updatedAt: row.updated_at,
52
+ lastActivityAt: row.last_activity_at || row.updated_at || row.created_at,
53
+ activeTimeMs: row.active_time_ms || 0,
54
+ };
55
+ }
56
+
57
+ /** Override getById to include computed last_activity_at and active_time_ms fields */
58
+ getById(id) {
59
+ const row = this.db
60
+ .prepare(`SELECT s.*, ${ACTIVITY_FIELDS_SQL} FROM sessions s WHERE s.id = ?`)
61
+ .get(id);
62
+ return this.map(row);
63
+ }
64
+
65
+ /** Create a new session with optional config (mode, thinkingEnabled, gitBranch, parentSessionId, status, model, effortLevel) */
66
+ create(projectId, name, prompt, options = {}) {
67
+ const config = parseCreateConfig(options, Array.prototype.slice.call(arguments, 4));
68
+
69
+ const id = databaseManager.generateId();
70
+ const now = Date.now();
71
+ this.db
72
+ .prepare(
73
+ `INSERT INTO sessions (id, project_id, name, status, mode, thinking_enabled, git_branch, parent_session_id, model, effort_level, created_at, updated_at)
74
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
75
+ )
76
+ .run(id, projectId, name, config.status, config.mode, config.thinkingEnabled ? 1 : 0, config.gitBranch, config.parentSessionId, config.model, config.effortLevel, now, now);
77
+
78
+ // Create initial conversation
79
+ const conversation = conversations.create(id, 'Initial', true);
80
+
81
+ // Only create initial user message for sessions that start immediately
82
+ // For waiting/scheduled sessions, the message will be created when they start
83
+ if (config.status !== 'waiting' && config.status !== 'scheduled') {
84
+ messages.create(id, 'user', prompt, { toolUse: null, conversationId: conversation.id });
85
+ }
86
+
87
+ return this.getById(id);
88
+ }
89
+
90
+ getByProjectId(projectId, { archived = null, starred = null, limit = null, offset = 0 } = {}) {
91
+ let sql = `SELECT s.*, ${ACTIVITY_FIELDS_SQL} FROM sessions s WHERE project_id = ?`;
92
+ const params = [projectId];
93
+
94
+ if (archived !== null) {
95
+ sql += ` AND archived = ?`;
96
+ params.push(archived ? 1 : 0);
97
+ }
98
+
99
+ if (starred !== null) {
100
+ sql += ` AND starred = ?`;
101
+ params.push(starred ? 1 : 0);
102
+ }
103
+
104
+ sql += ` ORDER BY
105
+ starred DESC,
106
+ updated_at DESC,
107
+ created_at DESC,
108
+ rowid DESC`;
109
+
110
+ // Add LIMIT/OFFSET for pagination
111
+ if (limit !== null) {
112
+ sql += ` LIMIT ? OFFSET ?`;
113
+ params.push(limit, offset);
114
+ }
115
+
116
+ const rows = this.db.prepare(sql).all(...params);
117
+ return this.mapAll(rows);
118
+ }
119
+
120
+ /** Get count of sessions for a project with optional archived/starred filters */
121
+ getCountByProjectId(projectId, { archived = null, starred = null } = {}) {
122
+ let sql = `SELECT COUNT(*) as count FROM sessions WHERE project_id = ?`;
123
+ const params = [projectId];
124
+
125
+ if (archived !== null) {
126
+ sql += ` AND archived = ?`;
127
+ params.push(archived ? 1 : 0);
128
+ }
129
+
130
+ if (starred !== null) {
131
+ sql += ` AND starred = ?`;
132
+ params.push(starred ? 1 : 0);
133
+ }
134
+
135
+ return this.db.prepare(sql).get(...params).count;
136
+ }
137
+
138
+ getActiveAndWaiting() {
139
+ const rows = this.db
140
+ .prepare(
141
+ `SELECT s.*, p.name as project_name, p.working_directory as project_working_directory, ${ACTIVITY_FIELDS_SQL}
142
+ FROM sessions s JOIN projects p ON s.project_id = p.id
143
+ WHERE s.status IN ('starting', 'running', 'waiting') AND s.archived = 0
144
+ ORDER BY s.starred DESC, s.updated_at DESC, s.created_at DESC, s.rowid DESC`
145
+ )
146
+ .all();
147
+ return rows.map(row => ({
148
+ ...SessionRepository.#mapSession(row),
149
+ projectName: row.project_name,
150
+ projectWorkingDirectory: row.project_working_directory,
151
+ }));
152
+ }
153
+
154
+ /** Get all child sessions of a parent session */
155
+ getChildSessions(parentSessionId) {
156
+ const rows = this.db
157
+ .prepare(
158
+ `SELECT s.*, ${ACTIVITY_FIELDS_SQL} FROM sessions s
159
+ WHERE parent_session_id = ?
160
+ ORDER BY updated_at DESC, created_at DESC, rowid DESC`
161
+ )
162
+ .all(parentSessionId);
163
+ return this.mapAll(rows);
164
+ }
165
+
166
+ /** Walk the parentSessionId chain upward to find the root session */
167
+ getRootSessionId(sessionId) {
168
+ let current = this.getById(sessionId);
169
+ const visited = new Set();
170
+
171
+ while (current?.parentSessionId) {
172
+ if (visited.has(current.id)) break; // cycle guard
173
+ visited.add(current.id);
174
+ current = this.getById(current.parentSessionId);
175
+ }
176
+
177
+ return current?.id ?? null;
178
+ }
179
+
180
+ /** Collect all descendant session IDs recursively (does NOT include the starting session) */
181
+ getAllDescendantIds(sessionId) {
182
+ const stmt = this.db.prepare('SELECT id FROM sessions WHERE parent_session_id = ?');
183
+ const descendantIds = [];
184
+ const stack = [sessionId];
185
+ const visited = new Set();
186
+
187
+ while (stack.length > 0) {
188
+ const currentId = stack.pop();
189
+ if (visited.has(currentId)) continue;
190
+ visited.add(currentId);
191
+
192
+ const childRows = stmt.all(currentId);
193
+ for (const row of childRows) {
194
+ descendantIds.push(row.id);
195
+ stack.push(row.id);
196
+ }
197
+ }
198
+
199
+ return descendantIds;
200
+ }
201
+
202
+ update(id, data) {
203
+ const { updates, values } = buildUpdateClauses(data);
204
+
205
+ if (updates.length === 0) return this.getById(id);
206
+
207
+ updates.push('updated_at = ?');
208
+ values.push(Date.now());
209
+ values.push(id);
210
+
211
+ this.db.prepare(`UPDATE sessions SET ${updates.join(', ')} WHERE id = ?`).run(...values);
212
+
213
+ return this.getById(id);
214
+ }
215
+
216
+ /** Duplicate a session with a new ID and reset state (does NOT handle git or conversation setup) */
217
+ duplicate(sourceSessionId, { name } = {}) {
218
+ const source = this.getById(sourceSessionId);
219
+ if (!source) {
220
+ throw new Error(`Session not found: ${sourceSessionId}`);
221
+ }
222
+
223
+ const id = databaseManager.generateId();
224
+ const now = Date.now();
225
+ const newName = name || `${source.name} (Copy)`;
226
+
227
+ // Insert new session with same settings but new ID and status
228
+ this.db
229
+ .prepare(
230
+ `INSERT INTO sessions (id, project_id, name, status, mode, thinking_enabled, git_branch, model, effort_level, context_window,
231
+ input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens,
232
+ web_search_requests, cost_usd, created_at, updated_at)
233
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
234
+ )
235
+ .run(
236
+ id,
237
+ source.projectId,
238
+ newName,
239
+ 'waiting', // Always reset status to draft
240
+ source.mode,
241
+ source.thinkingEnabled ? 1 : 0,
242
+ source.gitBranch, // Copy branch name (NOT worktree path)
243
+ source.model,
244
+ source.effortLevel,
245
+ source.contextWindow,
246
+ source.inputTokens,
247
+ source.outputTokens,
248
+ source.cacheReadInputTokens,
249
+ source.cacheCreationInputTokens,
250
+ source.webSearchRequests,
251
+ source.costUsd,
252
+ now,
253
+ now
254
+ );
255
+
256
+ return this.getById(id);
257
+ }
258
+
259
+ /** Get all sessions that have a PR URL set (used by prStatusService) */
260
+ getSessionsWithPrUrls() {
261
+ const rows = this.db
262
+ .prepare(
263
+ `SELECT s.*, ${ACTIVITY_FIELDS_SQL} FROM sessions s
264
+ WHERE pr_url IS NOT NULL ORDER BY updated_at DESC, created_at DESC, rowid DESC`
265
+ )
266
+ .all();
267
+ return this.mapAll(rows);
268
+ }
269
+
270
+ updateUsage(id, usage) {
271
+ this.db
272
+ .prepare(
273
+ `UPDATE sessions SET input_tokens = ?, output_tokens = ?, cache_read_input_tokens = ?,
274
+ cache_creation_input_tokens = ?, web_search_requests = ?, context_window = ?, updated_at = ?
275
+ WHERE id = ?`
276
+ )
277
+ .run(usage.inputTokens, usage.outputTokens, usage.cacheReadInputTokens,
278
+ usage.cacheCreationInputTokens, usage.webSearchRequests, usage.contextWindow, Date.now(), id);
279
+ return this.getById(id);
280
+ }
281
+
282
+ /** Get scheduled sessions that are due to start (scheduled_at <= now) */
283
+ getScheduledSessionsDue(now) {
284
+ const rows = this.db
285
+ .prepare(
286
+ `SELECT s.*, ${ACTIVITY_FIELDS_SQL} FROM sessions s
287
+ WHERE status = 'scheduled' AND scheduled_at IS NOT NULL AND scheduled_at <= ? AND archived = 0
288
+ ORDER BY scheduled_at ASC`
289
+ )
290
+ .all(now);
291
+ return this.mapAll(rows);
292
+ }
293
+
294
+ /** Get all scheduled sessions, optionally filtered by project */
295
+ getScheduledSessions(projectId = null) {
296
+ let sql = `SELECT s.*, p.name as project_name, ${ACTIVITY_FIELDS_SQL}
297
+ FROM sessions s JOIN projects p ON s.project_id = p.id
298
+ WHERE s.status = 'scheduled' AND s.archived = 0`;
299
+ const params = [];
300
+
301
+ if (projectId) {
302
+ sql += ` AND s.project_id = ?`;
303
+ params.push(projectId);
304
+ }
305
+
306
+ sql += ` ORDER BY s.scheduled_at ASC`;
307
+
308
+ const rows = this.db.prepare(sql).all(...params);
309
+ return rows.map(row => ({
310
+ ...SessionRepository.#mapSession(row),
311
+ projectName: row.project_name,
312
+ }));
313
+ }
314
+ }
@@ -0,0 +1,200 @@
1
+ import { BaseRepository } from './BaseRepository.js';
2
+ import { databaseManager } from './DatabaseManager.js';
3
+
4
+ /**
5
+ * Field definitions for session summary updates.
6
+ * Each entry defines how to transform the value for SQL.
7
+ */
8
+ const SUMMARY_FIELDS = {
9
+ shortSummary: { column: 'short_summary', transform: (v) => v },
10
+ fullSummary: { column: 'full_summary', transform: (v) => v },
11
+ keyActions: { column: 'key_actions', transform: (v) => JSON.stringify(v) },
12
+ filesModified: { column: 'files_modified', transform: (v) => JSON.stringify(v) },
13
+ outcome: { column: 'outcome', transform: (v) => v },
14
+ messageCount: { column: 'message_count', transform: (v) => v },
15
+ lastSummarizedMessageId: { column: 'last_summarized_message_id', transform: (v) => v },
16
+ prMerged: { column: 'pr_merged', transform: (v) => (v ? 1 : 0) },
17
+ prState: { column: 'pr_state', transform: (v) => v },
18
+ hasMergeConflicts: { column: 'has_merge_conflicts', transform: (v) => (v ? 1 : 0) },
19
+ ciStatus: { column: 'ci_status', transform: (v) => v },
20
+ ciFailures: { column: 'ci_failures', transform: (v) => JSON.stringify(v) },
21
+ };
22
+
23
+ /**
24
+ * Convert an optional boolean to a DB-compatible value (null, 0, or 1).
25
+ */
26
+ function optionalBoolToDb(value) {
27
+ if (value === undefined) return null;
28
+ return value ? 1 : 0;
29
+ }
30
+
31
+ /**
32
+ * Session summary repository class
33
+ */
34
+ export class SessionSummaryRepository extends BaseRepository {
35
+ constructor() {
36
+ super('session_summaries', SessionSummaryRepository.#mapSummary);
37
+ }
38
+
39
+ static #mapSummary(row) {
40
+ return {
41
+ id: row.id,
42
+ sessionId: row.session_id,
43
+ shortSummary: row.short_summary,
44
+ fullSummary: row.full_summary,
45
+ keyActions: row.key_actions ? JSON.parse(row.key_actions) : [],
46
+ filesModified: row.files_modified ? JSON.parse(row.files_modified) : [],
47
+ outcome: row.outcome,
48
+ messageCount: row.message_count,
49
+ lastSummarizedMessageId: row.last_summarized_message_id,
50
+ prMerged: row.pr_merged !== null ? Boolean(row.pr_merged) : null,
51
+ prState: row.pr_state,
52
+ hasMergeConflicts: row.has_merge_conflicts !== null ? Boolean(row.has_merge_conflicts) : null,
53
+ ciStatus: row.ci_status,
54
+ ciFailures: row.ci_failures ? JSON.parse(row.ci_failures) : [],
55
+ generatedAt: row.generated_at,
56
+ createdAt: row.created_at,
57
+ updatedAt: row.updated_at,
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Get summary by session ID
63
+ * @param {string} sessionId
64
+ * @returns {Object|null}
65
+ */
66
+ getBySessionId(sessionId) {
67
+ const row = this.db
68
+ .prepare('SELECT * FROM session_summaries WHERE session_id = ?')
69
+ .get(sessionId);
70
+ return this.map(row);
71
+ }
72
+
73
+ /**
74
+ * Create a new session summary
75
+ * @param {string} sessionId
76
+ * @param {Object} data
77
+ * @returns {Object}
78
+ */
79
+ create(sessionId, data) {
80
+ const id = databaseManager.generateId();
81
+ const now = Date.now();
82
+ this.db
83
+ .prepare(
84
+ `INSERT INTO session_summaries
85
+ (id, session_id, short_summary, full_summary, key_actions, files_modified, outcome, message_count, last_summarized_message_id, pr_merged, pr_state, has_merge_conflicts, ci_status, ci_failures, generated_at, created_at, updated_at)
86
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
87
+ )
88
+ .run(
89
+ id,
90
+ sessionId,
91
+ data.shortSummary,
92
+ data.fullSummary,
93
+ data.keyActions ? JSON.stringify(data.keyActions) : null,
94
+ data.filesModified ? JSON.stringify(data.filesModified) : null,
95
+ data.outcome || 'ongoing',
96
+ data.messageCount || 0,
97
+ data.lastSummarizedMessageId || null,
98
+ optionalBoolToDb(data.prMerged),
99
+ data.prState || null,
100
+ optionalBoolToDb(data.hasMergeConflicts),
101
+ data.ciStatus || null,
102
+ data.ciFailures ? JSON.stringify(data.ciFailures) : null,
103
+ now,
104
+ now,
105
+ now
106
+ );
107
+ return this.getById(id);
108
+ }
109
+
110
+ /**
111
+ * Update an existing session summary
112
+ * @param {string} id
113
+ * @param {Object} data
114
+ * @returns {Object}
115
+ */
116
+ update(id, data) {
117
+ const updates = [];
118
+ const values = [];
119
+
120
+ // Build update clauses from field definitions
121
+ for (const [key, def] of Object.entries(SUMMARY_FIELDS)) {
122
+ if (data[key] !== undefined) {
123
+ updates.push(`${def.column} = ?`);
124
+ values.push(def.transform(data[key]));
125
+ }
126
+ }
127
+
128
+ if (updates.length === 0) return this.getById(id);
129
+
130
+ // Always update generated_at and updated_at when updating
131
+ const now = Date.now();
132
+ updates.push('generated_at = ?', 'updated_at = ?');
133
+ values.push(now, now, id);
134
+
135
+ this.db.prepare(`UPDATE session_summaries SET ${updates.join(', ')} WHERE id = ?`).run(...values);
136
+
137
+ return this.getById(id);
138
+ }
139
+
140
+ /**
141
+ * Create or update a session summary (upsert)
142
+ * @param {string} sessionId
143
+ * @param {Object} data
144
+ * @returns {Object}
145
+ */
146
+ upsert(sessionId, data) {
147
+ const existing = this.getBySessionId(sessionId);
148
+ if (existing) {
149
+ return this.update(existing.id, data);
150
+ }
151
+ return this.create(sessionId, data);
152
+ }
153
+
154
+ /**
155
+ * Get summaries for multiple session IDs in a single query
156
+ * @param {string[]} sessionIds
157
+ * @returns {Object[]}
158
+ */
159
+ getBySessionIds(sessionIds) {
160
+ if (!sessionIds || sessionIds.length === 0) return [];
161
+ const placeholders = sessionIds.map(() => '?').join(',');
162
+ const rows = this.db
163
+ .prepare(`SELECT * FROM session_summaries WHERE session_id IN (${placeholders})`)
164
+ .all(...sessionIds);
165
+ return this.mapAll(rows);
166
+ }
167
+
168
+ /**
169
+ * Delete summary by session ID
170
+ * @param {string} sessionId
171
+ */
172
+ deleteBySessionId(sessionId) {
173
+ this.db.prepare('DELETE FROM session_summaries WHERE session_id = ?').run(sessionId);
174
+ }
175
+
176
+ /**
177
+ * Duplicates the session summary from one session to another.
178
+ * Note: PR-related fields (prMerged, prState, hasMergeConflicts, ciStatus, ciFailures)
179
+ * are NOT copied because the duplicated session is a fresh start and may create
180
+ * its own PR. Copying prMerged=true would prevent the summary from ever being
181
+ * regenerated (see summaryService.generateSummary skip logic).
182
+ * @param {string} sourceSessionId - Source session ID
183
+ * @param {string} targetSessionId - Target session ID
184
+ */
185
+ duplicateForSession(sourceSessionId, targetSessionId) {
186
+ const summary = this.getBySessionId(sourceSessionId);
187
+
188
+ if (summary) {
189
+ this.create(targetSessionId, {
190
+ shortSummary: summary.shortSummary,
191
+ fullSummary: summary.fullSummary,
192
+ keyActions: summary.keyActions,
193
+ filesModified: summary.filesModified,
194
+ outcome: summary.outcome,
195
+ messageCount: summary.messageCount,
196
+ // PR-related fields intentionally omitted - see note above
197
+ });
198
+ }
199
+ }
200
+ }
@@ -0,0 +1,171 @@
1
+ import { BaseRepository } from './BaseRepository.js';
2
+ import { databaseManager } from './DatabaseManager.js';
3
+
4
+ /**
5
+ * Session template repository class
6
+ */
7
+ export class SessionTemplateRepository extends BaseRepository {
8
+ constructor() {
9
+ super('session_templates', SessionTemplateRepository.#mapTemplate);
10
+ }
11
+
12
+ static #mapTemplate(row) {
13
+ return {
14
+ id: row.id,
15
+ projectId: row.project_id,
16
+ name: row.name,
17
+ prompt: row.prompt,
18
+ nextTemplateId: row.next_template_id,
19
+ thinkingEnabled: row.thinking_enabled === null ? null : Boolean(row.thinking_enabled),
20
+ gitBranch: row.git_branch,
21
+ gitMode: row.git_mode,
22
+ model: row.model || null,
23
+ mode: row.mode || null,
24
+ effortLevel: row.effort_level ?? null,
25
+ targetLaneId: row.target_lane_id || null,
26
+ createdAt: row.created_at,
27
+ updatedAt: row.updated_at,
28
+ };
29
+ }
30
+
31
+ /**
32
+ * Create a new session template
33
+ * @param {Object} data
34
+ * @param {string|null} data.projectId - null for global templates
35
+ * @param {string} data.name
36
+ * @param {string} data.prompt
37
+ * @param {string|null} data.nextTemplateId
38
+ * @param {boolean|null} data.thinkingEnabled
39
+ * @param {string|null} data.gitBranch
40
+ * @param {string|null} data.gitMode
41
+ * @param {string|null} data.model
42
+ * @param {string|null} data.mode
43
+ * @param {string|null} data.effortLevel
44
+ * @returns {Object}
45
+ */
46
+ /**
47
+ * Normalize thinkingEnabled to a DB-compatible value (null, 0, or 1).
48
+ */
49
+ static #normalizeThinkingEnabled(value) {
50
+ if (value === null || value === undefined) return null;
51
+ return value ? 1 : 0;
52
+ }
53
+
54
+ create(data) {
55
+ const id = databaseManager.generateId();
56
+ const now = Date.now();
57
+ this.db
58
+ .prepare(
59
+ `INSERT INTO session_templates (id, project_id, name, prompt, next_template_id, thinking_enabled, git_branch, git_mode, model, mode, effort_level, target_lane_id, created_at, updated_at)
60
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
61
+ )
62
+ .run(
63
+ id,
64
+ data.projectId || null,
65
+ data.name,
66
+ data.prompt,
67
+ data.nextTemplateId || null,
68
+ SessionTemplateRepository.#normalizeThinkingEnabled(data.thinkingEnabled),
69
+ data.gitBranch || null,
70
+ data.gitMode || null,
71
+ data.model || null,
72
+ data.mode !== undefined && data.mode !== null ? data.mode : null,
73
+ data.effortLevel ?? null,
74
+ data.targetLaneId || null,
75
+ now,
76
+ now
77
+ );
78
+ return this.getById(id);
79
+ }
80
+
81
+ /**
82
+ * Field mapping for update: property name -> { column, transform }
83
+ * Transform converts the JS value to the DB value.
84
+ */
85
+ static #updateFields = {
86
+ name: { column: 'name', transform: (v) => v },
87
+ prompt: { column: 'prompt', transform: (v) => v },
88
+ nextTemplateId: { column: 'next_template_id', transform: (v) => v },
89
+ thinkingEnabled: { column: 'thinking_enabled', transform: (v) => (v === null ? null : (v ? 1 : 0)) },
90
+ gitBranch: { column: 'git_branch', transform: (v) => v || null }, // Match create() behavior: empty string -> null
91
+ gitMode: { column: 'git_mode', transform: (v) => v },
92
+ model: { column: 'model', transform: (v) => v },
93
+ mode: { column: 'mode', transform: (v) => v },
94
+ effortLevel: { column: 'effort_level', transform: (v) => v },
95
+ targetLaneId: { column: 'target_lane_id', transform: (v) => v },
96
+ };
97
+
98
+ /**
99
+ * Update a session template
100
+ * @param {string} id
101
+ * @param {Object} data
102
+ * @returns {Object}
103
+ */
104
+ update(id, data) {
105
+ const updates = [];
106
+ const values = [];
107
+
108
+ for (const [key, def] of Object.entries(SessionTemplateRepository.#updateFields)) {
109
+ if (data[key] !== undefined) {
110
+ updates.push(`${def.column} = ?`);
111
+ values.push(def.transform(data[key]));
112
+ }
113
+ }
114
+
115
+ if (updates.length === 0) return this.getById(id);
116
+
117
+ updates.push('updated_at = ?');
118
+ values.push(Date.now());
119
+ values.push(id);
120
+
121
+ this.db.prepare(`UPDATE session_templates SET ${updates.join(', ')} WHERE id = ?`).run(...values);
122
+
123
+ return this.getById(id);
124
+ }
125
+
126
+ /**
127
+ * Get all global templates (projectId is null)
128
+ * @returns {Array}
129
+ */
130
+ getGlobal() {
131
+ const rows = this.db
132
+ .prepare('SELECT * FROM session_templates WHERE project_id IS NULL ORDER BY name')
133
+ .all();
134
+ return this.mapAll(rows);
135
+ }
136
+
137
+ /**
138
+ * Get templates by project ID
139
+ * @param {string} projectId
140
+ * @returns {Array}
141
+ */
142
+ getByProjectId(projectId) {
143
+ const rows = this.db
144
+ .prepare('SELECT * FROM session_templates WHERE project_id = ? ORDER BY name')
145
+ .all(projectId);
146
+ return this.mapAll(rows);
147
+ }
148
+
149
+ /**
150
+ * Get all templates available for a project (project-specific + global)
151
+ * @param {string} projectId
152
+ * @returns {Object} { project: Array, global: Array }
153
+ */
154
+ getAvailableForProject(projectId) {
155
+ return {
156
+ project: this.getByProjectId(projectId),
157
+ global: this.getGlobal(),
158
+ };
159
+ }
160
+
161
+ /**
162
+ * Get all templates
163
+ * @returns {Array}
164
+ */
165
+ getAll() {
166
+ const rows = this.db
167
+ .prepare('SELECT * FROM session_templates ORDER BY project_id NULLS FIRST, name')
168
+ .all();
169
+ return this.mapAll(rows);
170
+ }
171
+ }