circuschief 0.6.0 → 0.8.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 (187) hide show
  1. package/package.json +1 -1
  2. package/packages/server/src/agents/adapters/CodexAdapter.js +5 -4
  3. package/packages/server/src/api/canvas.js +22 -57
  4. package/packages/server/src/api/index.js +2 -0
  5. package/packages/server/src/api/kanban.js +4 -2
  6. package/packages/server/src/api/projects-helpers.js +20 -3
  7. package/packages/server/src/api/projects-session-helpers.js +35 -4
  8. package/packages/server/src/api/projects.js +11 -0
  9. package/packages/server/src/api/providers.js +11 -1
  10. package/packages/server/src/api/sessions-commands.js +35 -17
  11. package/packages/server/src/api/sessions-draft.js +1 -0
  12. package/packages/server/src/api/sessions-lifecycle.js +10 -10
  13. package/packages/server/src/api/sessions-patch.js +4 -0
  14. package/packages/server/src/api/sessions.js +6 -5
  15. package/packages/server/src/api/settings.js +52 -4
  16. package/packages/server/src/database.js +0 -2
  17. package/packages/server/src/db/ConversationRepository.js +16 -3
  18. package/packages/server/src/db/DatabaseManager.js +5 -1
  19. package/packages/server/src/db/ProjectDefaultsRepository.js +50 -40
  20. package/packages/server/src/db/ProviderRepository.js +87 -32
  21. package/packages/server/src/db/SessionRepository.js +13 -8
  22. package/packages/server/src/db/SettingsRepository.js +44 -16
  23. package/packages/server/src/db/conversation-helpers.js +1 -0
  24. package/packages/server/src/db/index.js +0 -3
  25. package/packages/server/src/db/migrations/index.js +36 -200
  26. package/packages/server/src/db/seedBaselineData.js +137 -0
  27. package/packages/server/src/db/session-helpers.js +9 -3
  28. package/packages/server/src/middleware/sessionLookup.js +81 -8
  29. package/packages/server/src/schema.sql +157 -132
  30. package/packages/server/src/scripts/backupDatabase.js +21 -0
  31. package/packages/server/src/scripts/dbUtils.js +81 -0
  32. package/packages/server/src/scripts/inspectDatabaseSchema.js +81 -0
  33. package/packages/server/src/scripts/validateDatabaseBaseline.js +212 -0
  34. package/packages/server/src/services/agentCallLogger.js +1 -1
  35. package/packages/server/src/services/codexSpawnHelper.js +9 -0
  36. package/packages/server/src/services/commandButtonPrompts.js +48 -0
  37. package/packages/server/src/services/commandRunner.js +7 -1
  38. package/packages/server/src/services/draftSessionService.js +2 -0
  39. package/packages/server/src/services/e2eSpawnCapture.js +147 -0
  40. package/packages/server/src/services/gitCommitAttribution.js +120 -0
  41. package/packages/server/src/services/gitService.js +11 -2
  42. package/packages/server/src/services/gitSessionSetup.js +11 -1
  43. package/packages/server/src/services/kanbanTriggers.js +6 -3
  44. package/packages/server/src/services/nodeSpawnHelper.js +9 -0
  45. package/packages/server/src/services/prUrlService.js +3 -3
  46. package/packages/server/src/services/queryParamBuilder.js +90 -0
  47. package/packages/server/src/services/sessionDuplicator.js +1 -5
  48. package/packages/server/src/services/sessionExecution.js +56 -106
  49. package/packages/server/src/services/sessionPrompts.js +16 -47
  50. package/packages/server/src/services/sessionProvider.js +16 -8
  51. package/packages/server/src/services/streamEventCallbacks.js +72 -40
  52. package/packages/server/src/services/streamEventHandler.js +13 -2
  53. package/packages/server/src/services/streamUsageHandler.js +6 -0
  54. package/packages/server/src/services/summaryClaudeClient.js +37 -12
  55. package/packages/server/src/services/summaryModelClient.js +154 -0
  56. package/packages/server/src/services/summaryModelResolver.js +148 -0
  57. package/packages/server/src/services/summaryService.js +11 -7
  58. package/packages/server/src/services/summaryStaleCheck.js +23 -4
  59. package/packages/server/src/services/templateTriggerService.js +3 -1
  60. package/packages/server/src/services/usageTracker.js +5 -1
  61. package/packages/server/src/services/visibleFinalErrorMessage.js +123 -0
  62. package/packages/shared/src/constants.js +4 -2
  63. package/packages/shared/src/contracts/commandButtons.js +16 -2
  64. package/packages/shared/src/contracts/projects.js +4 -2
  65. package/packages/shared/src/contracts/providers.js +60 -0
  66. package/packages/shared/src/contracts/sessions.js +2 -1
  67. package/packages/shared/src/contracts/templates.js +2 -2
  68. package/packages/shared/src/types.js +1 -9
  69. package/packages/shared/src/utils.js +11 -19
  70. package/packages/web/dist/assets/ActiveSessionsView-B0XHqLmv.js +1 -0
  71. package/packages/web/dist/assets/{AgentLogsView-Cdw4nmvd.js → AgentLogsView-DmsjUMlB.js} +2 -2
  72. package/packages/web/dist/assets/ApiClient-C3ztI9s9.js +1 -0
  73. package/packages/web/dist/assets/ArchiveConfirmModal-BlCyn5Vt.js +1 -0
  74. package/packages/web/dist/assets/ArchiveConfirmModal-DeoCVGXt.css +1 -0
  75. package/packages/web/dist/assets/{CommandButtonDetailView-DnFhJY5A.js → CommandButtonDetailView-CdSCPp78.js} +1 -1
  76. package/packages/web/dist/assets/EffortLevelSelector-hc2MNKg6.js +1 -0
  77. package/packages/web/dist/assets/{GeneralSettingsView-CQkmdczf.js → GeneralSettingsView-D1nI8_zk.js} +1 -1
  78. package/packages/web/dist/assets/InputWithButton-CAkttyqx.js +1 -0
  79. package/packages/web/dist/assets/{InputWithButton-cYdrEmTs.css → InputWithButton-D9HMvfR5.css} +1 -1
  80. package/packages/web/dist/assets/{InterpolationHelp-PfYR3KJo.js → InterpolationHelp-BO1j9Z3_.js} +1 -1
  81. package/packages/web/dist/assets/MarkdownEditor-ucRAP_UM.js +2 -0
  82. package/packages/web/dist/assets/{ModelSelector-BZOT1Jc6.css → ModelSelector-BSxKUSus.css} +1 -1
  83. package/packages/web/dist/assets/ModelSelector-CwTz8ZWO.js +1 -0
  84. package/packages/web/dist/assets/NewSessionView-BDPb-1qr.css +1 -0
  85. package/packages/web/dist/assets/NewSessionView-BsDrp8mj.js +3 -0
  86. package/packages/web/dist/assets/ProjectEditView-CwTOeSun.js +1 -0
  87. package/packages/web/dist/assets/ProjectEditView-J15mcsWz.css +1 -0
  88. package/packages/web/dist/assets/{ProjectListView-CuYMmd3O.js → ProjectListView-DcNyuINs.js} +1 -1
  89. package/packages/web/dist/assets/{ProjectNewView-CNaA4Maf.js → ProjectNewView-B5YV62hv.js} +1 -1
  90. package/packages/web/dist/assets/ProvidersView-bZemq_Rv.css +1 -0
  91. package/packages/web/dist/assets/ProvidersView-nY9GnDdO.js +1 -0
  92. package/packages/web/dist/assets/QuickResponseSettings-B352c75l.css +1 -0
  93. package/packages/web/dist/assets/QuickResponseSettings-BQwQXuL7.js +1 -0
  94. package/packages/web/dist/assets/{QuickResponsesPanel-DIBQFj0W.css → QuickResponsesPanel-BlFDvnZ2.css} +1 -1
  95. package/packages/web/dist/assets/{QuickResponsesPanel-BqMYSHb0.js → QuickResponsesPanel-BzSYcCSP.js} +1 -1
  96. package/packages/web/dist/assets/{ResizableTextarea-wYF3K2RO.js → ResizableTextarea-B3YIdIXv.js} +1 -1
  97. package/packages/web/dist/assets/{SessionCard-bLaQEWWX.js → SessionCard-CjE1tXiT.js} +1 -1
  98. package/packages/web/dist/assets/SessionDetailView-3cPZrbS3.js +36 -0
  99. package/packages/web/dist/assets/SessionDetailView-CZRZMrfM.css +1 -0
  100. package/packages/web/dist/assets/SessionFormOptions-B6AxyREh.js +1 -0
  101. package/packages/web/dist/assets/SessionFormOptions-BpUALRKn.css +1 -0
  102. package/packages/web/dist/assets/SessionListView-B5_6gW49.css +1 -0
  103. package/packages/web/dist/assets/SessionListView-CLXBfLcq.js +1 -0
  104. package/packages/web/dist/assets/{SessionLogStream-DTnDAF95.js → SessionLogStream-LlZ3z_Xj.js} +1 -1
  105. package/packages/web/dist/assets/{SettingsView-DNLUSsHV.js → SettingsView-CTGiGvR2.js} +1 -1
  106. package/packages/web/dist/assets/{SlashCommandWizard-CRGFaO8t.js → SlashCommandWizard-Cy04d7-o.js} +1 -1
  107. package/packages/web/dist/assets/{SlashCommandWizard-Dn7sNaBd.css → SlashCommandWizard-DJzw3LP5.css} +1 -1
  108. package/packages/web/dist/assets/SummarySettingsView-BR2ZjEa3.js +1 -0
  109. package/packages/web/dist/assets/SummarySettingsView-l2bxHmZZ.css +1 -0
  110. package/packages/web/dist/assets/TemplateDetailView-DH6Oswsp.js +1 -0
  111. package/packages/web/dist/assets/{commandButtons-Bbjf3fCt.js → commandButtons-BfqR-fqq.js} +1 -1
  112. package/packages/web/dist/assets/index-1zziPL6l.js +1 -0
  113. package/packages/web/dist/assets/index-7kzHPxSF.js +1 -0
  114. package/packages/web/dist/assets/index-B0N_obMc.js +1 -0
  115. package/packages/web/dist/assets/index-BNk_gdfI.js +1 -0
  116. package/packages/web/dist/assets/{index-gmCCsCQ1.css → index-BY174HVJ.css} +1 -1
  117. package/packages/web/dist/assets/index-CSqaAH-0.js +1 -0
  118. package/packages/web/dist/assets/index-C_q4WlK8.js +1 -0
  119. package/packages/web/dist/assets/index-D1wpU4y0.js +7 -0
  120. package/packages/web/dist/assets/index-D5zCA8sD.js +1 -0
  121. package/packages/web/dist/assets/index-DGR8ELWY.js +1 -0
  122. package/packages/web/dist/assets/index-DHga8pXo.js +1 -0
  123. package/packages/web/dist/assets/index-DSby02Wl.js +1 -0
  124. package/packages/web/dist/assets/{index-Cf6vdW-B.js → index-DgkC10TW.js} +3 -3
  125. package/packages/web/dist/assets/index-DqjXJTVI.js +1 -0
  126. package/packages/web/dist/assets/{index-Bs7Qf5D6.js → index-DtfUt785.js} +2 -2
  127. package/packages/web/dist/assets/index-_4S2uLDI.js +1 -0
  128. package/packages/web/dist/assets/{index-BQL_L4gL.js → index-fK8FIZgP.js} +15 -14
  129. package/packages/web/dist/assets/index-gmiZeFXN.js +1 -0
  130. package/packages/web/dist/assets/index-irD539ZM.js +3 -0
  131. package/packages/web/dist/assets/index-yq-E1Y00.js +1 -0
  132. package/packages/web/dist/assets/{projects-CPt3AB7U.js → projects-DXYQNJIi.js} +1 -1
  133. package/packages/web/dist/assets/{providers-ChfeMvUq.js → providers-1bnH-exJ.js} +1 -1
  134. package/packages/web/dist/assets/sessions-6zGUlFrt.js +1 -0
  135. package/packages/web/dist/assets/settings-MbfRir0d.js +1 -0
  136. package/packages/web/dist/index.html +2 -2
  137. package/packages/server/src/api/sessions-notes.js +0 -51
  138. package/packages/server/src/db/SessionNoteRepository.js +0 -60
  139. package/packages/server/src/db/migrations/canvasItemsMigrations.js +0 -109
  140. package/packages/server/src/db/migrations/conversationsMigrations.js +0 -183
  141. package/packages/server/src/db/migrations/kanbanMigrations.js +0 -99
  142. package/packages/server/src/db/migrations/miscMigrations.js +0 -369
  143. package/packages/server/src/db/migrations/projectsMigrations.js +0 -99
  144. package/packages/server/src/db/migrations/sessionsMigrations.js +0 -282
  145. package/packages/web/dist/assets/ActiveSessionsView-UCbQrF1b.js +0 -1
  146. package/packages/web/dist/assets/ApiClient-CWbXWDUY.js +0 -1
  147. package/packages/web/dist/assets/ArchiveConfirmModal-BQ-4gI0R.css +0 -1
  148. package/packages/web/dist/assets/ArchiveConfirmModal-J48eh3zw.js +0 -1
  149. package/packages/web/dist/assets/EffortLevelSelector-bXbPo4Zw.js +0 -1
  150. package/packages/web/dist/assets/InputWithButton-XyM3k6lN.js +0 -1
  151. package/packages/web/dist/assets/MarkdownEditor-P8F5kO-o.js +0 -2
  152. package/packages/web/dist/assets/ModelSelector-CowKfGMP.js +0 -1
  153. package/packages/web/dist/assets/NewSessionView-D_Hi7M9g.css +0 -1
  154. package/packages/web/dist/assets/NewSessionView-DkjFLvHU.js +0 -3
  155. package/packages/web/dist/assets/ProjectEditView-CpeKj-_w.css +0 -1
  156. package/packages/web/dist/assets/ProjectEditView-embVT7NC.js +0 -1
  157. package/packages/web/dist/assets/ProvidersView-C7rydtOd.js +0 -1
  158. package/packages/web/dist/assets/ProvidersView-DE82G_5W.css +0 -1
  159. package/packages/web/dist/assets/QuickResponseSettings-B8188A1D.css +0 -1
  160. package/packages/web/dist/assets/QuickResponseSettings-BTQEKhwJ.js +0 -1
  161. package/packages/web/dist/assets/SessionDetailView-Cv-xMzXp.css +0 -1
  162. package/packages/web/dist/assets/SessionDetailView-CvQOUsW2.js +0 -36
  163. package/packages/web/dist/assets/SessionFormOptions-3pzbgI2Q.js +0 -1
  164. package/packages/web/dist/assets/SessionFormOptions-DhhIkjIS.css +0 -1
  165. package/packages/web/dist/assets/SessionListView-Dranfb72.js +0 -1
  166. package/packages/web/dist/assets/SessionListView-fHlQyecX.css +0 -1
  167. package/packages/web/dist/assets/SummarySettingsView-C7G_suHp.js +0 -1
  168. package/packages/web/dist/assets/SummarySettingsView-DcsmSVJI.css +0 -1
  169. package/packages/web/dist/assets/TemplateDetailView-B78_DLMR.js +0 -1
  170. package/packages/web/dist/assets/index--V7c-VZf.js +0 -1
  171. package/packages/web/dist/assets/index-8Q04yd7H.js +0 -1
  172. package/packages/web/dist/assets/index-B47XRBDH.js +0 -1
  173. package/packages/web/dist/assets/index-BXbgZrhS.js +0 -1
  174. package/packages/web/dist/assets/index-CGhDVPen.js +0 -1
  175. package/packages/web/dist/assets/index-CKcRO1A6.js +0 -1
  176. package/packages/web/dist/assets/index-CTq-SLIW.js +0 -1
  177. package/packages/web/dist/assets/index-CYyos3iC.js +0 -1
  178. package/packages/web/dist/assets/index-CsCREAxF.js +0 -1
  179. package/packages/web/dist/assets/index-DJTTk_8T.js +0 -3
  180. package/packages/web/dist/assets/index-DPqUJ5JK.js +0 -1
  181. package/packages/web/dist/assets/index-EwAe1dKg.js +0 -1
  182. package/packages/web/dist/assets/index-JBA8axyA.js +0 -1
  183. package/packages/web/dist/assets/index-JkVHFtK5.js +0 -7
  184. package/packages/web/dist/assets/index-gMPUwT55.js +0 -1
  185. package/packages/web/dist/assets/index-wadc_0zT.js +0 -1
  186. package/packages/web/dist/assets/sessions-CwPsJOb1.js +0 -1
  187. package/packages/web/dist/assets/settings-BOj6wq6t.js +0 -1
@@ -8,7 +8,6 @@ import { requireSession, requireSessionAndProject } from '../middleware/sessionL
8
8
  import { commandRunner } from '../services/commandRunner.js';
9
9
 
10
10
  // Import sub-routers
11
- import notesRouter from './sessions-notes.js';
12
11
  import conversationsRouter from './sessions-conversations.js';
13
12
  import commandsRouter from './sessions-commands.js';
14
13
  import patchRouter from './sessions-patch.js';
@@ -21,7 +20,6 @@ import draftRouter from './sessions-draft.js';
21
20
  const router = Router();
22
21
 
23
22
  // Mount sub-routers
24
- router.use('/', notesRouter);
25
23
  router.use('/', conversationsRouter);
26
24
  router.use('/', commandsRouter);
27
25
  router.use('/', patchRouter);
@@ -108,11 +106,14 @@ router.get('/:id', requireSession, (req, res) => {
108
106
  const allMessages = messages.getBySessionId(req.params.id);
109
107
  const hasResponses = allMessages.some(msg => msg.role === 'assistant');
110
108
 
111
- // Get command run statuses (latest run per button for this session)
109
+ // Session details are for the requested session; command runs are workflow-root scoped.
110
+ const commandRunSessionId = sessions.getRootSessionId(req.params.id) || req.params.id;
111
+
112
+ // Get command run statuses (latest run per button for this workflow)
112
113
  // Completed runs from DB
113
- const dbRuns = commandRuns.getLatestRunsForSession(req.params.id);
114
+ const dbRuns = commandRuns.getLatestRunsForSession(commandRunSessionId);
114
115
  // Currently running commands from memory - filter all runs for this session
115
- const allRunning = commandRunner.getRunsBySession(req.params.id);
116
+ const allRunning = commandRunner.getRunsBySession(commandRunSessionId);
116
117
  const runningRuns = allRunning.filter(run => run.status === 'running');
117
118
 
118
119
  // Build map of buttonId -> run data
@@ -1,8 +1,9 @@
1
1
  import { Router } from 'express';
2
- import { settings } from '../db/index.js';
2
+ import { modelProviders, settings } from '../db/index.js';
3
3
  import { DEFAULT_SESSION_TITLE_PROMPT } from '../services/summaryService.js';
4
4
 
5
5
  const router = Router();
6
+ const SUPPORTED_SUMMARY_PROVIDER_KINDS = new Set(['anthropic', 'openai']);
6
7
 
7
8
  /**
8
9
  * GET /api/settings/token-weights
@@ -93,19 +94,42 @@ router.get('/summary', (req, res) => {
93
94
  */
94
95
  router.put('/summary', (req, res) => {
95
96
  try {
96
- const { disableSessionSummaries, sessionTitlePrompt } = req.body;
97
+ const body = req.body || {};
98
+ const {
99
+ disableSessionSummaries,
100
+ sessionTitlePrompt,
101
+ summaryModel,
102
+ summaryProviderId,
103
+ } = body;
97
104
 
98
105
  // Validate that all required fields are present
99
106
  if (typeof disableSessionSummaries !== 'boolean' ||
100
- typeof sessionTitlePrompt !== 'string') {
107
+ typeof sessionTitlePrompt !== 'string' ||
108
+ !Object.prototype.hasOwnProperty.call(body, 'summaryModel') ||
109
+ !Object.prototype.hasOwnProperty.call(body, 'summaryProviderId')) {
101
110
  return res.status(400).json({
102
- error: 'Invalid summary settings. disableSessionSummaries must be a boolean, sessionTitlePrompt must be a string'
111
+ error: 'Invalid summary settings. disableSessionSummaries must be a boolean, sessionTitlePrompt must be a string, summaryModel must be present, and summaryProviderId must be present'
103
112
  });
104
113
  }
105
114
 
115
+ if (typeof summaryModel !== 'string') {
116
+ return res.status(400).json({ error: 'summaryModel must be a string' });
117
+ }
118
+
119
+ if (summaryProviderId !== null && typeof summaryProviderId !== 'string') {
120
+ return res.status(400).json({ error: 'summaryProviderId must be a string or null' });
121
+ }
122
+
123
+ const validationError = validateSummaryModelSelection(summaryModel, summaryProviderId);
124
+ if (validationError) {
125
+ return res.status(400).json({ error: validationError });
126
+ }
127
+
106
128
  const updatedSettings = settings.setSummarySettings({
107
129
  disableSessionSummaries,
108
130
  sessionTitlePrompt,
131
+ summaryModel,
132
+ summaryProviderId,
109
133
  });
110
134
 
111
135
  // Include the default prompt for UI display/editing
@@ -119,6 +143,30 @@ router.put('/summary', (req, res) => {
119
143
  }
120
144
  });
121
145
 
146
+ function validateSummaryModelSelection(summaryModel, summaryProviderId) {
147
+ if (!summaryModel) {
148
+ if (summaryProviderId !== null) {
149
+ return 'summaryProviderId must be null when summaryModel is empty';
150
+ }
151
+ return null;
152
+ }
153
+
154
+ if (!summaryProviderId) {
155
+ return 'summaryProviderId is required when summaryModel is set';
156
+ }
157
+
158
+ const provider = modelProviders.getById(summaryProviderId);
159
+ if (!provider) return `Unknown summary provider: ${summaryProviderId}`;
160
+ if (!SUPPORTED_SUMMARY_PROVIDER_KINDS.has(provider.kind || 'anthropic')) {
161
+ return `Unsupported summary provider kind: ${provider.kind}`;
162
+ }
163
+ const ownsModel = provider.models?.some((model) => model.modelId === summaryModel);
164
+ if (!ownsModel) {
165
+ return `Provider ${summaryProviderId} does not own summary model ${summaryModel}`;
166
+ }
167
+ return null;
168
+ }
169
+
122
170
  /**
123
171
  * DELETE /api/settings/summary
124
172
  * Reset summary settings to defaults
@@ -10,7 +10,6 @@ export {
10
10
  MessageRepository,
11
11
  ConversationRepository,
12
12
  CanvasItemRepository,
13
- SessionNoteRepository,
14
13
  TodoRepository,
15
14
  WorkLogRepository,
16
15
  SessionSummaryRepository,
@@ -35,7 +34,6 @@ export {
35
34
  messages,
36
35
  conversations,
37
36
  canvasItems,
38
- sessionNotes,
39
37
  todos,
40
38
  workLogs,
41
39
  sessionSummaries,
@@ -237,6 +237,7 @@ export class ConversationRepository extends BaseRepository {
237
237
  * @param {Object} usage - Usage data
238
238
  * @param {number} usage.inputTokens
239
239
  * @param {number} usage.outputTokens
240
+ * @param {number} usage.thinkingTokens
240
241
  * @param {number} usage.cacheReadInputTokens
241
242
  * @param {number} usage.cacheCreationInputTokens
242
243
  * @param {number} usage.webSearchRequests
@@ -255,6 +256,7 @@ export class ConversationRepository extends BaseRepository {
255
256
  `UPDATE conversations SET
256
257
  input_tokens = ?,
257
258
  output_tokens = ?,
259
+ thinking_tokens = ?,
258
260
  cache_read_input_tokens = ?,
259
261
  cache_creation_input_tokens = ?,
260
262
  web_search_requests = ?,
@@ -265,6 +267,7 @@ export class ConversationRepository extends BaseRepository {
265
267
  .run(
266
268
  usage.inputTokens,
267
269
  usage.outputTokens,
270
+ usage.thinkingTokens || 0,
268
271
  usage.cacheReadInputTokens,
269
272
  usage.cacheCreationInputTokens,
270
273
  usage.webSearchRequests,
@@ -290,9 +293,12 @@ export class ConversationRepository extends BaseRepository {
290
293
  const now = Date.now();
291
294
 
292
295
  this.db
293
- .prepare(
294
- `INSERT INTO conversations (id, session_id, name, summary, is_active, created_at, updated_at)
295
- VALUES (?, ?, ?, ?, ?, ?, ?)`
296
+ .prepare(
297
+ `INSERT INTO conversations (id, session_id, name, summary, is_active,
298
+ input_tokens, output_tokens, thinking_tokens,
299
+ cache_read_input_tokens, cache_creation_input_tokens,
300
+ web_search_requests, context_window, created_at, updated_at)
301
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
296
302
  )
297
303
  .run(
298
304
  id,
@@ -300,6 +306,13 @@ export class ConversationRepository extends BaseRepository {
300
306
  conv.name,
301
307
  conv.summary,
302
308
  conv.isActive ? 1 : 0,
309
+ conv.inputTokens,
310
+ conv.outputTokens,
311
+ conv.thinkingTokens,
312
+ conv.cacheReadInputTokens,
313
+ conv.cacheCreationInputTokens,
314
+ conv.webSearchRequests,
315
+ conv.contextWindow,
303
316
  now,
304
317
  now
305
318
  );
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'url';
4
4
  import { dirname, join } from 'path';
5
5
  import crypto from 'crypto';
6
6
  import { allMigrations } from './migrations/index.js';
7
+ import { seedBaselineData } from './seedBaselineData.js';
7
8
 
8
9
  const __filename = fileURLToPath(import.meta.url);
9
10
  const __dirname = dirname(__filename);
@@ -32,7 +33,10 @@ export class DatabaseManager {
32
33
  const schema = readFileSync(join(__dirname, '..', 'schema.sql'), 'utf-8');
33
34
  this.#db.exec(schema);
34
35
 
35
- // Run migrations for existing databases
36
+ // Seed required baseline data before future migrations run.
37
+ seedBaselineData(this.#db);
38
+
39
+ // Run post-baseline migrations for existing databases.
36
40
  this.#runMigrations();
37
41
 
38
42
  return this.#db;
@@ -12,6 +12,7 @@ const UPSERT_FIELD_MAP = [
12
12
  { key: 'gitMode', column: 'git_mode' },
13
13
  { key: 'gitBranch', column: 'git_branch' },
14
14
  { key: 'model', column: 'model' },
15
+ { key: 'providerId', column: 'provider_id' },
15
16
  { key: 'effortLevel', column: 'effort_level' },
16
17
  ];
17
18
 
@@ -47,6 +48,7 @@ export class ProjectDefaultsRepository extends BaseRepository {
47
48
  gitMode: row.git_mode || null,
48
49
  gitBranch: row.git_branch || null,
49
50
  model: row.model || null,
51
+ providerId: row.provider_id || null,
50
52
  effortLevel: row.effort_level || null,
51
53
  createdAt: row.created_at,
52
54
  updatedAt: row.updated_at,
@@ -72,47 +74,53 @@ export class ProjectDefaultsRepository extends BaseRepository {
72
74
  * @param {Object} data - Defaults data (all fields optional)
73
75
  * @returns {Object} Updated defaults object
74
76
  */
77
+ #insertDefaults(projectId, data) {
78
+ const id = databaseManager.generateId();
79
+ const now = Date.now();
80
+
81
+ this.db
82
+ .prepare(
83
+ `INSERT INTO project_session_defaults
84
+ (id, project_id, mode, thinking_enabled, start_immediately, git_mode, git_branch, model, provider_id, effort_level, created_at, updated_at)
85
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
86
+ )
87
+ .run(
88
+ id,
89
+ projectId,
90
+ data.mode || null,
91
+ data.thinkingEnabled !== undefined ? (data.thinkingEnabled ? 1 : 0) : null,
92
+ data.startImmediately !== undefined ? (data.startImmediately ? 1 : 0) : null,
93
+ data.gitMode || null,
94
+ data.gitBranch || null,
95
+ data.model || null,
96
+ data.providerId || null,
97
+ data.effortLevel || null,
98
+ now,
99
+ now
100
+ );
101
+ }
102
+
103
+ #updateDefaults(projectId, data) {
104
+ const { updates, values } = buildUpdateFields(data);
105
+
106
+ if (updates.length > 0) {
107
+ updates.push('updated_at = ?');
108
+ values.push(Date.now());
109
+ values.push(projectId);
110
+
111
+ this.db
112
+ .prepare(`UPDATE project_session_defaults SET ${updates.join(', ')} WHERE project_id = ?`)
113
+ .run(...values);
114
+ }
115
+ }
116
+
75
117
  upsert(projectId, data) {
76
- // Get existing defaults if any
77
118
  const existing = this.getByProjectId(projectId);
78
119
 
79
120
  if (!existing) {
80
- // Create new defaults record
81
- const id = databaseManager.generateId();
82
- const now = Date.now();
83
-
84
- this.db
85
- .prepare(
86
- `INSERT INTO project_session_defaults
87
- (id, project_id, mode, thinking_enabled, start_immediately, git_mode, git_branch, model, effort_level, created_at, updated_at)
88
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
89
- )
90
- .run(
91
- id,
92
- projectId,
93
- data.mode || null,
94
- data.thinkingEnabled !== undefined ? (data.thinkingEnabled ? 1 : 0) : null,
95
- data.startImmediately !== undefined ? (data.startImmediately ? 1 : 0) : null,
96
- data.gitMode || null,
97
- data.gitBranch || null,
98
- data.model || null,
99
- data.effortLevel || null,
100
- now,
101
- now
102
- );
121
+ this.#insertDefaults(projectId, data);
103
122
  } else {
104
- // Update existing defaults
105
- const { updates, values } = buildUpdateFields(data);
106
-
107
- if (updates.length > 0) {
108
- updates.push('updated_at = ?');
109
- values.push(Date.now());
110
- values.push(projectId);
111
-
112
- this.db
113
- .prepare(`UPDATE project_session_defaults SET ${updates.join(', ')} WHERE project_id = ?`)
114
- .run(...values);
115
- }
123
+ this.#updateDefaults(projectId, data);
116
124
  }
117
125
 
118
126
  return this.getByProjectId(projectId);
@@ -137,7 +145,8 @@ export class ProjectDefaultsRepository extends BaseRepository {
137
145
  .prepare(
138
146
  `UPDATE project_session_defaults
139
147
  SET mode = NULL, thinking_enabled = NULL, start_immediately = NULL,
140
- git_mode = NULL, git_branch = NULL, model = NULL, effort_level = NULL, updated_at = ?
148
+ git_mode = NULL, git_branch = NULL, model = NULL, provider_id = NULL,
149
+ effort_level = NULL, updated_at = ?
141
150
  WHERE project_id = ?`
142
151
  )
143
152
  .run(Date.now(), projectId);
@@ -161,12 +170,13 @@ export class ProjectDefaultsRepository extends BaseRepository {
161
170
  */
162
171
  static getSystemDefaults() {
163
172
  return {
164
- mode: 'standard',
165
- thinkingEnabled: false,
173
+ mode: 'yolo',
174
+ thinkingEnabled: true,
166
175
  startImmediately: true,
167
- gitMode: null,
176
+ gitMode: 'worktree',
168
177
  gitBranch: null,
169
178
  model: null,
179
+ providerId: null,
170
180
  effortLevel: null,
171
181
  };
172
182
  }
@@ -1,6 +1,7 @@
1
1
  import { BaseRepository } from './BaseRepository.js';
2
2
  import { databaseManager } from './DatabaseManager.js';
3
3
  import { encrypt, decrypt } from '../services/encryption.js';
4
+ import { normalizeCommitAttributionOverride } from '../../../shared/src/contracts/providers.js';
4
5
 
5
6
  /**
6
7
  * Valid values for `providers.kind`. Maps 1:1 to an agent adapter:
@@ -18,6 +19,55 @@ export const AGENT_TYPE_BY_KIND = Object.freeze({
18
19
  openai: 'codex',
19
20
  });
20
21
 
22
+ const BUILT_IN_MUTABLE_FIELDS = Object.freeze(['commitAttributionOverride']);
23
+
24
+ const UPDATE_COLUMN_BUILDERS = Object.freeze({
25
+ name: (value) => ['name = ?', value],
26
+ baseUrl: (value) => ['base_url = ?', value],
27
+ authToken: (value) => ['auth_token = ?', encrypt(value)],
28
+ apiTimeoutMs: (value) => ['api_timeout_ms = ?', value],
29
+ additionalEnvVars: (value) => [
30
+ 'additional_env_vars = ?',
31
+ value ? JSON.stringify(value) : null,
32
+ ],
33
+ commitAttributionOverride: (value) => [
34
+ 'commit_attribution_override = ?',
35
+ normalizeCommitAttributionOverride(value),
36
+ ],
37
+ });
38
+
39
+ function validateBuiltInUpdate(provider, data) {
40
+ if (!provider.isBuiltIn) return;
41
+
42
+ const unsupportedFields = Object.keys(data || {}).filter(
43
+ (key) => !BUILT_IN_MUTABLE_FIELDS.includes(key)
44
+ );
45
+ if (unsupportedFields.length > 0) {
46
+ throw new Error(
47
+ `Built-in providers can only update commitAttributionOverride. Rejected fields: ${unsupportedFields.join(', ')}.`
48
+ );
49
+ }
50
+ }
51
+
52
+ function validateKindImmutable(data) {
53
+ if (!data || !Object.prototype.hasOwnProperty.call(data, 'kind')) return;
54
+
55
+ throw new Error(
56
+ "Provider kind is immutable after create. Delete and recreate the provider to change kind."
57
+ );
58
+ }
59
+
60
+ function buildUpdateColumns(data = {}) {
61
+ return Object.entries(UPDATE_COLUMN_BUILDERS).reduce((result, [field, buildColumn]) => {
62
+ if (data[field] === undefined) return result;
63
+
64
+ const [update, value] = buildColumn(data[field]);
65
+ result.updates.push(update);
66
+ result.values.push(value);
67
+ return result;
68
+ }, { updates: [], values: [] });
69
+ }
70
+
21
71
  /**
22
72
  * Provider repository class (replaces ModelProviderRepository).
23
73
  *
@@ -44,6 +94,7 @@ export class ProviderRepository extends BaseRepository {
44
94
  authToken: decrypt(row.auth_token),
45
95
  apiTimeoutMs: row.api_timeout_ms,
46
96
  additionalEnvVars: row.additional_env_vars ? JSON.parse(row.additional_env_vars) : null,
97
+ commitAttributionOverride: row.commit_attribution_override ?? null,
47
98
  isBuiltIn: row.is_built_in === 1,
48
99
  kind: row.kind || 'anthropic',
49
100
  createdAt: row.created_at,
@@ -83,6 +134,7 @@ export class ProviderRepository extends BaseRepository {
83
134
  authToken = null,
84
135
  apiTimeoutMs = null,
85
136
  additionalEnvVars = null,
137
+ commitAttributionOverride = null,
86
138
  kind = 'anthropic',
87
139
  } = data;
88
140
 
@@ -95,8 +147,8 @@ export class ProviderRepository extends BaseRepository {
95
147
 
96
148
  this.db
97
149
  .prepare(
98
- `INSERT INTO providers (id, name, base_url, auth_token, api_timeout_ms, additional_env_vars, kind, created_at, updated_at)
99
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
150
+ `INSERT INTO providers (id, name, base_url, auth_token, api_timeout_ms, additional_env_vars, commit_attribution_override, kind, created_at, updated_at)
151
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
100
152
  )
101
153
  .run(
102
154
  id,
@@ -105,6 +157,7 @@ export class ProviderRepository extends BaseRepository {
105
157
  encrypt(authToken),
106
158
  apiTimeoutMs,
107
159
  additionalEnvVars ? JSON.stringify(additionalEnvVars) : null,
160
+ normalizeCommitAttributionOverride(commitAttributionOverride),
108
161
  kind,
109
162
  now,
110
163
  now
@@ -143,38 +196,13 @@ export class ProviderRepository extends BaseRepository {
143
196
  * @returns {Object} Updated provider (with models array)
144
197
  */
145
198
  update(id, data) {
146
- // `kind` is immutable after create. Existing models + env wiring depend on it,
147
- // so changing it in place would silently corrupt sessions already attached to
148
- // this provider.
149
- if (data && Object.prototype.hasOwnProperty.call(data, 'kind')) {
150
- throw new Error(
151
- "Provider kind is immutable after create. Delete and recreate the provider to change kind."
152
- );
153
- }
199
+ const provider = this.getById(id);
200
+ if (!provider) return null;
154
201
 
155
- const updates = [];
156
- const values = [];
202
+ validateBuiltInUpdate(provider, data);
203
+ validateKindImmutable(data);
157
204
 
158
- if (data.name !== undefined) {
159
- updates.push('name = ?');
160
- values.push(data.name);
161
- }
162
- if (data.baseUrl !== undefined) {
163
- updates.push('base_url = ?');
164
- values.push(data.baseUrl);
165
- }
166
- if (data.authToken !== undefined) {
167
- updates.push('auth_token = ?');
168
- values.push(encrypt(data.authToken));
169
- }
170
- if (data.apiTimeoutMs !== undefined) {
171
- updates.push('api_timeout_ms = ?');
172
- values.push(data.apiTimeoutMs);
173
- }
174
- if (data.additionalEnvVars !== undefined) {
175
- updates.push('additional_env_vars = ?');
176
- values.push(data.additionalEnvVars ? JSON.stringify(data.additionalEnvVars) : null);
177
- }
205
+ const { updates, values } = buildUpdateColumns(data);
178
206
 
179
207
  if (updates.length > 0) {
180
208
  updates.push('updated_at = ?');
@@ -348,6 +376,33 @@ export class ProviderRepository extends BaseRepository {
348
376
  return provider;
349
377
  }
350
378
 
379
+ /**
380
+ * Look up provider metadata for a model without applying runtime env fallback
381
+ * rules. Unlike getProviderByModelId, this returns built-in Anthropic too.
382
+ *
383
+ * @param {string|null|undefined} modelId
384
+ * @returns {Object|null}
385
+ */
386
+ getProviderMetadataByModelId(modelId) {
387
+ if (!modelId) return null;
388
+
389
+ const tierNames = ['sonnet', 'opus', 'haiku'];
390
+ if (tierNames.includes(modelId.toLowerCase())) {
391
+ return this.getById('anthropic-default');
392
+ }
393
+
394
+ const row = this.db
395
+ .prepare(
396
+ `SELECT p.id FROM providers p
397
+ JOIN provider_models pm ON p.id = pm.provider_id
398
+ WHERE pm.model_id = ?
399
+ ORDER BY p.is_built_in ASC, p.name ASC`
400
+ )
401
+ .get(modelId);
402
+
403
+ return row ? this.getById(row.id) : null;
404
+ }
405
+
351
406
  /**
352
407
  * Resolve a provider's agent type from its id.
353
408
  * @param {string|null|undefined} providerId
@@ -45,6 +45,7 @@ export class SessionRepository extends BaseRepository {
45
45
  status: row.status,
46
46
  mode: row.mode,
47
47
  model: row.model,
48
+ providerId: row.provider_id || null,
48
49
  thinkingEnabled: Boolean(row.thinking_enabled),
49
50
  archived: Boolean(row.archived),
50
51
  starred: Boolean(row.starred),
@@ -52,6 +53,7 @@ export class SessionRepository extends BaseRepository {
52
53
  gitBranch: row.git_branch,
53
54
  gitWorktree: row.git_worktree,
54
55
  prUrl: row.pr_url,
56
+ prUrlAutoLinkDisabled: Boolean(row.pr_url_auto_link_disabled),
55
57
  error: row.error,
56
58
  costUsd: row.cost_usd,
57
59
  claudeSessionId: row.claude_session_id,
@@ -84,7 +86,7 @@ export class SessionRepository extends BaseRepository {
84
86
  return this.map(row);
85
87
  }
86
88
 
87
- /** Create a new session with optional config (mode, thinkingEnabled, gitBranch, parentSessionId, status, model, effortLevel, agentType) */
89
+ /** Create a new session with optional config (mode, thinkingEnabled, gitBranch, parentSessionId, status, model, providerId, effortLevel, agentType) */
88
90
  create(projectId, name, prompt, options = {}) {
89
91
  const config = parseCreateConfig(options, Array.prototype.slice.call(arguments, 4));
90
92
 
@@ -98,8 +100,8 @@ export class SessionRepository extends BaseRepository {
98
100
  const now = Date.now();
99
101
  this.db
100
102
  .prepare(
101
- `INSERT INTO sessions (id, project_id, name, status, mode, thinking_enabled, git_branch, parent_session_id, model, effort_level, agent_type, created_at, updated_at)
102
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
103
+ `INSERT INTO sessions (id, project_id, name, status, mode, thinking_enabled, git_branch, parent_session_id, model, provider_id, effort_level, agent_type, created_at, updated_at)
104
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
103
105
  )
104
106
  .run(
105
107
  id,
@@ -111,6 +113,7 @@ export class SessionRepository extends BaseRepository {
111
113
  config.gitBranch,
112
114
  config.parentSessionId,
113
115
  config.model,
116
+ config.providerId,
114
117
  config.effortLevel,
115
118
  agentType,
116
119
  now,
@@ -282,10 +285,10 @@ export class SessionRepository extends BaseRepository {
282
285
  // Insert new session with same settings but new ID and status
283
286
  this.db
284
287
  .prepare(
285
- `INSERT INTO sessions (id, project_id, name, status, mode, thinking_enabled, git_branch, model, effort_level, agent_type, context_window,
286
- input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens,
288
+ `INSERT INTO sessions (id, project_id, name, status, mode, thinking_enabled, git_branch, model, provider_id, effort_level, agent_type, context_window,
289
+ input_tokens, output_tokens, thinking_tokens, cache_read_input_tokens, cache_creation_input_tokens,
287
290
  web_search_requests, cost_usd, created_at, updated_at)
288
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
291
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
289
292
  )
290
293
  .run(
291
294
  id,
@@ -296,11 +299,13 @@ export class SessionRepository extends BaseRepository {
296
299
  source.thinkingEnabled ? 1 : 0,
297
300
  source.gitBranch, // Copy branch name (NOT worktree path)
298
301
  source.model,
302
+ source.providerId,
299
303
  source.effortLevel,
300
304
  source.agentType || DEFAULT_AGENT_TYPE,
301
305
  source.contextWindow,
302
306
  source.inputTokens,
303
307
  source.outputTokens,
308
+ source.thinkingTokens,
304
309
  source.cacheReadInputTokens,
305
310
  source.cacheCreationInputTokens,
306
311
  source.webSearchRequests,
@@ -326,11 +331,11 @@ export class SessionRepository extends BaseRepository {
326
331
  updateUsage(id, usage) {
327
332
  this.db
328
333
  .prepare(
329
- `UPDATE sessions SET input_tokens = ?, output_tokens = ?, cache_read_input_tokens = ?,
334
+ `UPDATE sessions SET input_tokens = ?, output_tokens = ?, thinking_tokens = ?, cache_read_input_tokens = ?,
330
335
  cache_creation_input_tokens = ?, web_search_requests = ?, context_window = ?, updated_at = ?
331
336
  WHERE id = ?`
332
337
  )
333
- .run(usage.inputTokens, usage.outputTokens, usage.cacheReadInputTokens,
338
+ .run(usage.inputTokens, usage.outputTokens, usage.thinkingTokens || 0, usage.cacheReadInputTokens,
334
339
  usage.cacheCreationInputTokens, usage.webSearchRequests, usage.contextWindow, Date.now(), id);
335
340
  return this.getById(id);
336
341
  }