circuschief 0.8.0 → 1.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.
- package/package.json +1 -1
- package/packages/server/src/agents/AgentGateway.js +2 -0
- package/packages/server/src/agents/adapters/GeminiAdapter.js +105 -0
- package/packages/server/src/agents/adapters/cliUtils.js +15 -0
- package/packages/server/src/agents/adapters/codexCliRunner.js +1 -8
- package/packages/server/src/agents/adapters/geminiCliRunner.js +183 -0
- package/packages/server/src/agents/adapters/geminiEventMapper.js +195 -0
- package/packages/server/src/api/commandButtons.js +16 -15
- package/packages/server/src/api/projects-commandButtons.js +6 -6
- package/packages/server/src/api/projects-session-create.js +109 -0
- package/packages/server/src/api/projects-session-defaults.js +51 -0
- package/packages/server/src/api/projects-session-helpers.js +47 -1
- package/packages/server/src/api/projects-templates.js +38 -0
- package/packages/server/src/api/projects.js +28 -180
- package/packages/server/src/api/sessions-commands.js +21 -18
- package/packages/server/src/api/sessions-patch.js +41 -1
- package/packages/server/src/db/ProviderRepository.js +4 -2
- package/packages/server/src/db/SessionRepository.js +1 -1
- package/packages/server/src/db/SessionTemplateRepository.js +23 -2
- package/packages/server/src/db/migrations/canvasItemsMigrations.js +109 -0
- package/packages/server/src/db/migrations/conversationsMigrations.js +187 -0
- package/packages/server/src/db/migrations/index.js +234 -6
- package/packages/server/src/db/migrations/kanbanMigrations.js +99 -0
- package/packages/server/src/db/migrations/miscMigrations.js +244 -0
- package/packages/server/src/db/migrations/projectsMigrations.js +130 -0
- package/packages/server/src/db/migrations/providerCommitAttributionMigrations.js +30 -0
- package/packages/server/src/db/migrations/providerMigrations.js +250 -0
- package/packages/server/src/db/migrations/sessionTableRecreate.js +136 -0
- package/packages/server/src/db/migrations/sessionsMigrations.js +300 -0
- package/packages/server/src/db/seedBaselineData.js +23 -1
- package/packages/server/src/db/session-helpers.js +26 -1
- package/packages/server/src/schema.sql +5 -1
- package/packages/server/src/services/commandButtonPrompts.js +9 -7
- package/packages/server/src/services/e2eSpawnCapture.js +47 -6
- package/packages/server/src/services/geminiSpawnHelper.js +47 -0
- package/packages/server/src/services/gitCommitAttribution.js +38 -8
- package/packages/server/src/services/gitDiff.js +107 -0
- package/packages/server/src/services/gitRepoUrl.js +174 -0
- package/packages/server/src/services/gitService.js +43 -311
- package/packages/server/src/services/gitWorktree.js +127 -0
- package/packages/server/src/services/providerTestService.js +59 -1
- package/packages/server/src/services/queryParamBuilder.js +33 -1
- package/packages/server/src/services/sessionExecution.js +4 -0
- package/packages/server/src/services/sessionPrompts.js +23 -1
- package/packages/server/src/services/sessionProvider.js +41 -1
- package/packages/shared/src/constants.js +1 -1
- package/packages/shared/src/contracts/providers.js +1 -1
- package/packages/shared/src/contracts/sessions.js +27 -1
- package/packages/shared/src/contracts/templates.js +10 -0
- package/packages/shared/src/types.js +7 -0
- package/packages/web/dist/assets/{ActiveSessionsView-B0XHqLmv.js → ActiveSessionsView-EdNxmPmZ.js} +1 -1
- package/packages/web/dist/assets/{AgentLogsView-DmsjUMlB.js → AgentLogsView-C2wX0JPP.js} +2 -2
- package/packages/web/dist/assets/ApiClient-DfbJwzpz.js +1 -0
- package/packages/web/dist/assets/ArchiveConfirmModal-DJERn5XO.js +1 -0
- package/packages/web/dist/assets/CommandButtonDetailView-CBPI8-US.js +1 -0
- package/packages/web/dist/assets/CommandButtonDetailView-D9zjx9ME.css +1 -0
- package/packages/web/dist/assets/EffortLevelSelector-PaBpUveC.js +1 -0
- package/packages/web/dist/assets/{GeneralSettingsView-D1nI8_zk.js → GeneralSettingsView-Dw-x83R0.js} +1 -1
- package/packages/web/dist/assets/{InputWithButton-CAkttyqx.js → InputWithButton-CHHcpF4I.js} +1 -1
- package/packages/web/dist/assets/{InterpolationHelp-BO1j9Z3_.js → InterpolationHelp-CLNPz8s8.js} +1 -1
- package/packages/web/dist/assets/MarkdownEditor-DYi1igfT.js +2 -0
- package/packages/web/dist/assets/ModelSelector-Cko_yTO5.js +1 -0
- package/packages/web/dist/assets/{ModelSelector-BSxKUSus.css → ModelSelector-Dtwe5xLH.css} +1 -1
- package/packages/web/dist/assets/{NewSessionView-BDPb-1qr.css → NewSessionView-DBl7T2Xp.css} +1 -1
- package/packages/web/dist/assets/NewSessionView-DwUfBg70.js +3 -0
- package/packages/web/dist/assets/ProjectEditView-CSbsea3U.js +1 -0
- package/packages/web/dist/assets/ProjectEditView-DbqTbA0q.css +1 -0
- package/packages/web/dist/assets/{ProjectListView-DcNyuINs.js → ProjectListView-CEc_LWZL.js} +1 -1
- package/packages/web/dist/assets/{ProjectNewView-B5YV62hv.js → ProjectNewView-D4U0uRlp.js} +1 -1
- package/packages/web/dist/assets/ProvidersView-2KCOiY6Q.css +1 -0
- package/packages/web/dist/assets/ProvidersView-CD1j8BOv.js +1 -0
- package/packages/web/dist/assets/QuickResponsesPanel-Dp39f12o.js +1 -0
- package/packages/web/dist/assets/QuickResponsesPanel-dk-Rj8xx.css +1 -0
- package/packages/web/dist/assets/ResizableTextarea-BWywIqOv.js +1 -0
- package/packages/web/dist/assets/ResizableTextarea-DERSH3Wz.css +1 -0
- package/packages/web/dist/assets/SessionCard-B6d5ijDW.js +1 -0
- package/packages/web/dist/assets/SessionDetailView-DWbXdx7A.js +36 -0
- package/packages/web/dist/assets/SessionDetailView-ULeIkWS0.css +1 -0
- package/packages/web/dist/assets/{SessionFormOptions-B6AxyREh.js → SessionFormOptions-Dz9ik4Fo.js} +1 -1
- package/packages/web/dist/assets/{SessionListView-B5_6gW49.css → SessionListView-3-xx6EVs.css} +1 -1
- package/packages/web/dist/assets/SessionListView-C129buBe.js +1 -0
- package/packages/web/dist/assets/{SessionLogStream-LlZ3z_Xj.js → SessionLogStream-BvXUNNBZ.js} +6 -6
- package/packages/web/dist/assets/{SettingsView-CTGiGvR2.js → SettingsView-DW1NvpX_.js} +1 -1
- package/packages/web/dist/assets/SlashCommandWizard-DleYBxrE.js +1 -0
- package/packages/web/dist/assets/{SummarySettingsView-BR2ZjEa3.js → SummarySettingsView-CLUfcWvf.js} +1 -1
- package/packages/web/dist/assets/TemplateDetailView-B5NI2oTR.css +1 -0
- package/packages/web/dist/assets/TemplateDetailView-Cukb205e.js +1 -0
- package/packages/web/dist/assets/{commandButtons-BfqR-fqq.js → commandButtons-DejH0rVN.js} +1 -1
- package/packages/web/dist/assets/index-BD7Y3rBE.js +3 -0
- package/packages/web/dist/assets/{index-BY174HVJ.css → index-Bd20AzX1.css} +1 -1
- package/packages/web/dist/assets/index-BgJiarKe.js +1 -0
- package/packages/web/dist/assets/index-Bk32fSSG.js +1 -0
- package/packages/web/dist/assets/index-BkA6pF2Z.js +1 -0
- package/packages/web/dist/assets/index-Cltr-Ldt.js +7 -0
- package/packages/web/dist/assets/index-Co-46Tp3.js +1 -0
- package/packages/web/dist/assets/index-Cpykk857.js +1 -0
- package/packages/web/dist/assets/index-CtABl0D1.js +1 -0
- package/packages/web/dist/assets/index-Cuqk5m9S.js +1 -0
- package/packages/web/dist/assets/{index-fK8FIZgP.js → index-CvXApbVC.js} +15 -15
- package/packages/web/dist/assets/index-D2gN-xEH.js +1 -0
- package/packages/web/dist/assets/index-Dd3WpmyQ.js +1 -0
- package/packages/web/dist/assets/index-Dk6--9rj.js +1 -0
- package/packages/web/dist/assets/{index-DgkC10TW.js → index-MZf7MlPX.js} +3 -3
- package/packages/web/dist/assets/{index-DtfUt785.js → index-NShCcwfj.js} +1 -1
- package/packages/web/dist/assets/index-hA3VEuSq.js +1 -0
- package/packages/web/dist/assets/index-p0mp3nca.js +1 -0
- package/packages/web/dist/assets/index-qntNa5r_.js +1 -0
- package/packages/web/dist/assets/index-qq9ceNSK.js +1 -0
- package/packages/web/dist/assets/projectDefaults-D9xkp2XR.js +1 -0
- package/packages/web/dist/assets/{projects-DXYQNJIi.js → projects-BvLADGKx.js} +1 -1
- package/packages/web/dist/assets/{providers-1bnH-exJ.js → providers-DZ-fOa4G.js} +1 -1
- package/packages/web/dist/assets/{sessions-6zGUlFrt.js → sessions-DETEyjPI.js} +1 -1
- package/packages/web/dist/assets/{settings-MbfRir0d.js → settings-TWfbahn5.js} +1 -1
- package/packages/web/dist/index.html +2 -2
- package/packages/web/dist/assets/ApiClient-C3ztI9s9.js +0 -1
- package/packages/web/dist/assets/ArchiveConfirmModal-BlCyn5Vt.js +0 -1
- package/packages/web/dist/assets/CommandButtonDetailView-CdSCPp78.js +0 -1
- package/packages/web/dist/assets/CommandButtonDetailView-DBm3rzhw.css +0 -1
- package/packages/web/dist/assets/EffortLevelSelector-hc2MNKg6.js +0 -1
- package/packages/web/dist/assets/MarkdownEditor-ucRAP_UM.js +0 -2
- package/packages/web/dist/assets/ModelSelector-CwTz8ZWO.js +0 -1
- package/packages/web/dist/assets/NewSessionView-BsDrp8mj.js +0 -3
- package/packages/web/dist/assets/ProjectEditView-CwTOeSun.js +0 -1
- package/packages/web/dist/assets/ProjectEditView-J15mcsWz.css +0 -1
- package/packages/web/dist/assets/ProvidersView-bZemq_Rv.css +0 -1
- package/packages/web/dist/assets/ProvidersView-nY9GnDdO.js +0 -1
- package/packages/web/dist/assets/QuickResponseSettings-B352c75l.css +0 -1
- package/packages/web/dist/assets/QuickResponseSettings-BQwQXuL7.js +0 -1
- package/packages/web/dist/assets/QuickResponsesPanel-BlFDvnZ2.css +0 -1
- package/packages/web/dist/assets/QuickResponsesPanel-BzSYcCSP.js +0 -1
- package/packages/web/dist/assets/ResizableTextarea-B3YIdIXv.js +0 -1
- package/packages/web/dist/assets/ResizableTextarea-DsU3TVwF.css +0 -1
- package/packages/web/dist/assets/SessionCard-CjE1tXiT.js +0 -1
- package/packages/web/dist/assets/SessionDetailView-3cPZrbS3.js +0 -36
- package/packages/web/dist/assets/SessionDetailView-CZRZMrfM.css +0 -1
- package/packages/web/dist/assets/SessionListView-CLXBfLcq.js +0 -1
- package/packages/web/dist/assets/SlashCommandWizard-Cy04d7-o.js +0 -1
- package/packages/web/dist/assets/TemplateDetailView-DH6Oswsp.js +0 -1
- package/packages/web/dist/assets/TemplateDetailView-DT2m06W7.css +0 -1
- package/packages/web/dist/assets/index-1zziPL6l.js +0 -1
- package/packages/web/dist/assets/index-7kzHPxSF.js +0 -1
- package/packages/web/dist/assets/index-B0N_obMc.js +0 -1
- package/packages/web/dist/assets/index-BNk_gdfI.js +0 -1
- package/packages/web/dist/assets/index-CSqaAH-0.js +0 -1
- package/packages/web/dist/assets/index-C_q4WlK8.js +0 -1
- package/packages/web/dist/assets/index-D1wpU4y0.js +0 -7
- package/packages/web/dist/assets/index-D5zCA8sD.js +0 -1
- package/packages/web/dist/assets/index-DGR8ELWY.js +0 -1
- package/packages/web/dist/assets/index-DHga8pXo.js +0 -1
- package/packages/web/dist/assets/index-DSby02Wl.js +0 -1
- package/packages/web/dist/assets/index-DqjXJTVI.js +0 -1
- package/packages/web/dist/assets/index-_4S2uLDI.js +0 -1
- package/packages/web/dist/assets/index-gmiZeFXN.js +0 -1
- package/packages/web/dist/assets/index-irD539ZM.js +0 -3
- package/packages/web/dist/assets/index-yq-E1Y00.js +0 -1
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
|
-
import { sessions, sessionTemplates, modelProviders } from '../database.js';
|
|
2
|
+
import { sessions, sessionTemplates, modelProviders, sessionSummaries } from '../database.js';
|
|
3
3
|
import { broadcastToSession, broadcastToProject } from '../websocket.js';
|
|
4
4
|
import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
|
|
5
5
|
import * as summaryService from '../services/summaryService.js';
|
|
6
6
|
import { setSessionNameFromPr } from '../services/prUrlService.js';
|
|
7
|
+
import { checkSessionCiStatusNow } from '../services/prStatusService.js';
|
|
8
|
+
import { broadcastSummaryUpdate } from '../services/summaryBroadcast.js';
|
|
7
9
|
import { requireSession } from '../middleware/sessionLookup.js';
|
|
8
10
|
|
|
9
11
|
const router = Router();
|
|
@@ -195,6 +197,30 @@ function broadcastSessionUpdate(sessionId, projectId, updated, updateData) {
|
|
|
195
197
|
});
|
|
196
198
|
}
|
|
197
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Reset all PR state fields in the session summary when the PR URL changes or is cleared.
|
|
202
|
+
* This ensures stale PR state (e.g., "merged") doesn't persist for a different PR
|
|
203
|
+
* and doesn't block summary regeneration.
|
|
204
|
+
* @param {string} sessionId
|
|
205
|
+
* @param {string|null} projectId - For broadcasting to project subscribers
|
|
206
|
+
*/
|
|
207
|
+
function resetPrStateForSession(sessionId, projectId) {
|
|
208
|
+
const existingSummary = sessionSummaries.getBySessionId(sessionId);
|
|
209
|
+
if (!existingSummary) return;
|
|
210
|
+
|
|
211
|
+
sessionSummaries.upsert(sessionId, {
|
|
212
|
+
prState: null,
|
|
213
|
+
prMerged: false,
|
|
214
|
+
hasMergeConflicts: false,
|
|
215
|
+
ciStatus: null,
|
|
216
|
+
ciFailures: [],
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Broadcast the reset to both session and project subscribers
|
|
220
|
+
const updatedSummary = sessionSummaries.getBySessionId(sessionId);
|
|
221
|
+
broadcastSummaryUpdate(sessionId, projectId, updatedSummary);
|
|
222
|
+
}
|
|
223
|
+
|
|
198
224
|
// PATCH /api/sessions/:id - Update session settings
|
|
199
225
|
router.patch('/:id', requireSession, (req, res) => {
|
|
200
226
|
const { updateData, error } = buildUpdateData(req.body);
|
|
@@ -209,6 +235,15 @@ router.patch('/:id', requireSession, (req, res) => {
|
|
|
209
235
|
|
|
210
236
|
const updated = sessions.update(req.params.id, updateData);
|
|
211
237
|
|
|
238
|
+
// Reset PR state when URL changes to a different PR or is cleared
|
|
239
|
+
const previousPrUrl = req.session_.prUrl;
|
|
240
|
+
const prUrlProvided = Object.prototype.hasOwnProperty.call(updateData, 'prUrl');
|
|
241
|
+
const prUrlChanged = prUrlProvided && previousPrUrl && previousPrUrl !== updateData.prUrl;
|
|
242
|
+
|
|
243
|
+
if (prUrlChanged) {
|
|
244
|
+
resetPrStateForSession(req.params.id, req.session_.projectId);
|
|
245
|
+
}
|
|
246
|
+
|
|
212
247
|
// Propagate PR URL to parent session if set (not when clearing)
|
|
213
248
|
if (updateData.prUrl) {
|
|
214
249
|
summaryService.propagatePrUrlToParent(req.params.id, updateData.prUrl);
|
|
@@ -218,6 +253,11 @@ router.patch('/:id', requireSession, (req, res) => {
|
|
|
218
253
|
setSessionNameFromPr(req.params.id, updateData.prUrl).catch(err => {
|
|
219
254
|
console.error(`[Sessions API] Failed to set session name from PR:`, err);
|
|
220
255
|
});
|
|
256
|
+
|
|
257
|
+
// Trigger immediate PR status check for the new/changed URL
|
|
258
|
+
checkSessionCiStatusNow(req.params.id).catch(err => {
|
|
259
|
+
console.error(`[Sessions API] Failed to check PR status after URL change:`, err);
|
|
260
|
+
});
|
|
221
261
|
}
|
|
222
262
|
|
|
223
263
|
broadcastSessionUpdate(req.params.id, req.session_.projectId, updated, updateData);
|
|
@@ -5,10 +5,11 @@ import { normalizeCommitAttributionOverride } from '../../../shared/src/contract
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Valid values for `providers.kind`. Maps 1:1 to an agent adapter:
|
|
8
|
-
* - 'anthropic'
|
|
8
|
+
* - 'anthropic' ��� 'claude-code'
|
|
9
9
|
* - 'openai' → 'codex'
|
|
10
|
+
* - 'google' → 'gemini'
|
|
10
11
|
*/
|
|
11
|
-
export const PROVIDER_KINDS = Object.freeze(['anthropic', 'openai']);
|
|
12
|
+
export const PROVIDER_KINDS = Object.freeze(['anthropic', 'openai', 'google']);
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Mapping from provider kind to the agent adapter that should drive sessions
|
|
@@ -17,6 +18,7 @@ export const PROVIDER_KINDS = Object.freeze(['anthropic', 'openai']);
|
|
|
17
18
|
export const AGENT_TYPE_BY_KIND = Object.freeze({
|
|
18
19
|
anthropic: 'claude-code',
|
|
19
20
|
openai: 'codex',
|
|
21
|
+
google: 'gemini',
|
|
20
22
|
});
|
|
21
23
|
|
|
22
24
|
const BUILT_IN_MUTABLE_FIELDS = Object.freeze(['commitAttributionOverride']);
|
|
@@ -74,6 +74,7 @@ export class SessionRepository extends BaseRepository {
|
|
|
74
74
|
createdAt: row.created_at,
|
|
75
75
|
updatedAt: row.updated_at,
|
|
76
76
|
lastActivityAt: row.last_activity_at ?? null,
|
|
77
|
+
lastMessageAt: row.last_message_at ?? null,
|
|
77
78
|
activeTimeMs: row.active_time_ms || 0,
|
|
78
79
|
};
|
|
79
80
|
}
|
|
@@ -147,7 +148,6 @@ export class SessionRepository extends BaseRepository {
|
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
sql += ` ORDER BY
|
|
150
|
-
starred DESC,
|
|
151
151
|
COALESCE(last_activity_at, updated_at, created_at) DESC,
|
|
152
152
|
updated_at DESC,
|
|
153
153
|
created_at DESC,
|
|
@@ -23,6 +23,10 @@ export class SessionTemplateRepository extends BaseRepository {
|
|
|
23
23
|
mode: row.mode || null,
|
|
24
24
|
effortLevel: row.effort_level ?? null,
|
|
25
25
|
targetLaneId: row.target_lane_id || null,
|
|
26
|
+
showInQuickResponses: Boolean(row.show_in_quick_responses),
|
|
27
|
+
quickResponseAutoSubmit: Boolean(row.quick_response_auto_submit),
|
|
28
|
+
quickResponseSortOrder: row.quick_response_sort_order ?? 0,
|
|
29
|
+
legacyQuickResponseId: row.legacy_quick_response_id || null,
|
|
26
30
|
createdAt: row.created_at,
|
|
27
31
|
updatedAt: row.updated_at,
|
|
28
32
|
};
|
|
@@ -51,13 +55,22 @@ export class SessionTemplateRepository extends BaseRepository {
|
|
|
51
55
|
return value ? 1 : 0;
|
|
52
56
|
}
|
|
53
57
|
|
|
58
|
+
static #normalizeBoolean(value) {
|
|
59
|
+
return value ? 1 : 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
54
62
|
create(data) {
|
|
55
63
|
const id = databaseManager.generateId();
|
|
56
64
|
const now = Date.now();
|
|
57
65
|
this.db
|
|
58
66
|
.prepare(
|
|
59
|
-
`INSERT INTO session_templates (
|
|
60
|
-
|
|
67
|
+
`INSERT INTO session_templates (
|
|
68
|
+
id, project_id, name, prompt, next_template_id, thinking_enabled,
|
|
69
|
+
git_branch, git_mode, model, mode, effort_level, target_lane_id,
|
|
70
|
+
show_in_quick_responses, quick_response_auto_submit,
|
|
71
|
+
quick_response_sort_order, legacy_quick_response_id,
|
|
72
|
+
created_at, updated_at
|
|
73
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
61
74
|
)
|
|
62
75
|
.run(
|
|
63
76
|
id,
|
|
@@ -72,6 +85,10 @@ export class SessionTemplateRepository extends BaseRepository {
|
|
|
72
85
|
data.mode !== undefined && data.mode !== null ? data.mode : null,
|
|
73
86
|
data.effortLevel ?? null,
|
|
74
87
|
data.targetLaneId || null,
|
|
88
|
+
SessionTemplateRepository.#normalizeBoolean(data.showInQuickResponses),
|
|
89
|
+
SessionTemplateRepository.#normalizeBoolean(data.quickResponseAutoSubmit),
|
|
90
|
+
data.quickResponseSortOrder ?? 0,
|
|
91
|
+
data.legacyQuickResponseId || null,
|
|
75
92
|
now,
|
|
76
93
|
now
|
|
77
94
|
);
|
|
@@ -93,6 +110,10 @@ export class SessionTemplateRepository extends BaseRepository {
|
|
|
93
110
|
mode: { column: 'mode', transform: (v) => v },
|
|
94
111
|
effortLevel: { column: 'effort_level', transform: (v) => v },
|
|
95
112
|
targetLaneId: { column: 'target_lane_id', transform: (v) => v },
|
|
113
|
+
showInQuickResponses: { column: 'show_in_quick_responses', transform: (v) => v ? 1 : 0 },
|
|
114
|
+
quickResponseAutoSubmit: { column: 'quick_response_auto_submit', transform: (v) => v ? 1 : 0 },
|
|
115
|
+
quickResponseSortOrder: { column: 'quick_response_sort_order', transform: (v) => v },
|
|
116
|
+
legacyQuickResponseId: { column: 'legacy_quick_response_id', transform: (v) => v || null },
|
|
96
117
|
};
|
|
97
118
|
|
|
98
119
|
/**
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migrations for the canvas_items table.
|
|
3
|
+
* Each export is an array of { name, up(db) } migration objects.
|
|
4
|
+
*/
|
|
5
|
+
import { addColumnIfMissing, getColumns, getTableSql } from './migrationUtils.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Migrate canvas_items table to include 'code' in type CHECK constraint.
|
|
9
|
+
* SQLite doesn't support ALTER TABLE to modify constraints, so we recreate the table.
|
|
10
|
+
*/
|
|
11
|
+
function migrateCanvasItemsTypeConstraint(db) {
|
|
12
|
+
const tableSql = getTableSql(db, 'canvas_items');
|
|
13
|
+
|
|
14
|
+
// If schema already includes 'code', no migration needed
|
|
15
|
+
if (tableSql?.includes("'code'")) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
db.exec(`
|
|
20
|
+
CREATE TABLE canvas_items_new (
|
|
21
|
+
id TEXT PRIMARY KEY,
|
|
22
|
+
session_id TEXT REFERENCES sessions(id) ON DELETE CASCADE,
|
|
23
|
+
type TEXT NOT NULL CHECK (type IN ('image', 'markdown', 'text', 'json', 'pdf', 'code')),
|
|
24
|
+
content TEXT,
|
|
25
|
+
data TEXT,
|
|
26
|
+
mime_type TEXT,
|
|
27
|
+
filename TEXT,
|
|
28
|
+
label TEXT,
|
|
29
|
+
width INTEGER,
|
|
30
|
+
height INTEGER,
|
|
31
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
INSERT INTO canvas_items_new SELECT * FROM canvas_items;
|
|
35
|
+
|
|
36
|
+
DROP TABLE canvas_items;
|
|
37
|
+
|
|
38
|
+
ALTER TABLE canvas_items_new RENAME TO canvas_items;
|
|
39
|
+
|
|
40
|
+
CREATE INDEX IF NOT EXISTS idx_canvas_session ON canvas_items(session_id);
|
|
41
|
+
`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Migrate canvas_items table to drop label column.
|
|
46
|
+
*/
|
|
47
|
+
function migrateCanvasItemsDropLabel(db) {
|
|
48
|
+
const columns = getColumns(db, 'canvas_items');
|
|
49
|
+
|
|
50
|
+
// If label column doesn't exist, migration already done
|
|
51
|
+
if (!columns.includes('label')) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
db.exec(`
|
|
56
|
+
CREATE TABLE canvas_items_new (
|
|
57
|
+
id TEXT PRIMARY KEY,
|
|
58
|
+
session_id TEXT REFERENCES sessions(id) ON DELETE CASCADE,
|
|
59
|
+
type TEXT NOT NULL CHECK (type IN ('image', 'markdown', 'text', 'json', 'pdf', 'code')),
|
|
60
|
+
content TEXT,
|
|
61
|
+
data TEXT,
|
|
62
|
+
mime_type TEXT,
|
|
63
|
+
filename TEXT,
|
|
64
|
+
width INTEGER,
|
|
65
|
+
height INTEGER,
|
|
66
|
+
deleted_at INTEGER,
|
|
67
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
INSERT INTO canvas_items_new (id, session_id, type, content, data, mime_type, filename, width, height, deleted_at, created_at)
|
|
71
|
+
SELECT id, session_id, type, content, data, mime_type, filename, width, height, deleted_at, created_at FROM canvas_items;
|
|
72
|
+
|
|
73
|
+
DROP TABLE canvas_items;
|
|
74
|
+
|
|
75
|
+
ALTER TABLE canvas_items_new RENAME TO canvas_items;
|
|
76
|
+
|
|
77
|
+
CREATE INDEX IF NOT EXISTS idx_canvas_session ON canvas_items(session_id);
|
|
78
|
+
CREATE INDEX IF NOT EXISTS idx_canvas_deleted ON canvas_items(deleted_at);
|
|
79
|
+
`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** @type {Array<{name: string, up: (db: import('better-sqlite3').Database) => void}>} */
|
|
83
|
+
export const canvasItemsMigrations = [
|
|
84
|
+
{
|
|
85
|
+
name: 'canvas_items-migrate-type-constraint',
|
|
86
|
+
up(db) { migrateCanvasItemsTypeConstraint(db); },
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'canvas_items-add-deleted_at',
|
|
90
|
+
up(db) {
|
|
91
|
+
addColumnIfMissing(db, 'canvas_items', 'deleted_at', 'INTEGER');
|
|
92
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_canvas_deleted ON canvas_items(deleted_at)');
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'canvas_items-drop-label',
|
|
97
|
+
up(db) { migrateCanvasItemsDropLabel(db); },
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'canvas_items-add-updated_at',
|
|
101
|
+
up(db) {
|
|
102
|
+
const columns = getColumns(db, 'canvas_items');
|
|
103
|
+
if (!columns.includes('updated_at')) {
|
|
104
|
+
db.exec('ALTER TABLE canvas_items ADD COLUMN updated_at INTEGER');
|
|
105
|
+
db.exec('UPDATE canvas_items SET updated_at = created_at WHERE updated_at IS NULL');
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
];
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migrations for the conversations, conversation_messages, and message_attachments tables.
|
|
3
|
+
* Each export is an array of { name, up(db) } migration objects.
|
|
4
|
+
*/
|
|
5
|
+
import crypto from 'crypto';
|
|
6
|
+
import { addColumnIfMissing, getColumns } from './migrationUtils.js';
|
|
7
|
+
|
|
8
|
+
// Table name constants for migrations
|
|
9
|
+
const TABLE_CONVERSATIONS = 'conversations';
|
|
10
|
+
|
|
11
|
+
// Column type constants
|
|
12
|
+
const COL_INTEGER_DEFAULT_0 = 'INTEGER DEFAULT 0';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create default conversations for existing sessions that don't have any
|
|
16
|
+
* and associate orphaned messages with the default conversation.
|
|
17
|
+
*/
|
|
18
|
+
function migrateExistingSessionsToConversations(db) {
|
|
19
|
+
const sessionsWithoutConversations = db
|
|
20
|
+
.prepare(
|
|
21
|
+
`
|
|
22
|
+
SELECT DISTINCT s.id FROM sessions s
|
|
23
|
+
LEFT JOIN conversations c ON c.session_id = s.id
|
|
24
|
+
WHERE c.id IS NULL
|
|
25
|
+
AND EXISTS (SELECT 1 FROM conversation_messages m WHERE m.session_id = s.id)
|
|
26
|
+
`
|
|
27
|
+
)
|
|
28
|
+
.all();
|
|
29
|
+
|
|
30
|
+
for (const session of sessionsWithoutConversations) {
|
|
31
|
+
const convId = crypto.randomUUID();
|
|
32
|
+
const now = Date.now();
|
|
33
|
+
|
|
34
|
+
db.prepare(
|
|
35
|
+
`INSERT INTO conversations (id, session_id, name, is_active, created_at, updated_at)
|
|
36
|
+
VALUES (?, ?, ?, 1, ?, ?)`
|
|
37
|
+
).run(convId, session.id, 'Initial', now, now);
|
|
38
|
+
|
|
39
|
+
db.prepare(
|
|
40
|
+
`UPDATE conversation_messages SET conversation_id = ? WHERE session_id = ? AND conversation_id IS NULL`
|
|
41
|
+
).run(convId, session.id);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** @type {Array<{name: string, up: (db: import('better-sqlite3').Database) => void}>} */
|
|
46
|
+
export const conversationsMigrations = [
|
|
47
|
+
// --- Session summaries PR columns ---
|
|
48
|
+
{
|
|
49
|
+
name: 'session_summaries-add-pr_merged',
|
|
50
|
+
up(db) { addColumnIfMissing(db, 'session_summaries', 'pr_merged', 'INTEGER'); },
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'session_summaries-add-pr_state',
|
|
54
|
+
up(db) { addColumnIfMissing(db, 'session_summaries', 'pr_state', 'TEXT'); },
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'session_summaries-add-has_merge_conflicts',
|
|
58
|
+
up(db) { addColumnIfMissing(db, 'session_summaries', 'has_merge_conflicts', 'INTEGER'); },
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'session_summaries-add-ci_status',
|
|
62
|
+
up(db) { addColumnIfMissing(db, 'session_summaries', 'ci_status', 'TEXT'); },
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'session_summaries-add-ci_failures',
|
|
66
|
+
up(db) { addColumnIfMissing(db, 'session_summaries', 'ci_failures', 'TEXT'); },
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'session_summaries-add-last_summarized_message_id',
|
|
70
|
+
up(db) { addColumnIfMissing(db, 'session_summaries', 'last_summarized_message_id', 'TEXT'); },
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
// --- Message attachments ---
|
|
74
|
+
{
|
|
75
|
+
name: 'message_attachments-add-file_path',
|
|
76
|
+
up(db) { addColumnIfMissing(db, 'message_attachments', 'file_path', 'TEXT'); },
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
// --- Conversation messages: conversation_id ---
|
|
80
|
+
{
|
|
81
|
+
name: 'conversation_messages-add-conversation_id',
|
|
82
|
+
up(db) {
|
|
83
|
+
const columns = getColumns(db, 'conversation_messages');
|
|
84
|
+
if (!columns.includes('conversation_id')) {
|
|
85
|
+
db.exec(
|
|
86
|
+
'ALTER TABLE conversation_messages ADD COLUMN conversation_id TEXT REFERENCES conversations(id) ON DELETE CASCADE'
|
|
87
|
+
);
|
|
88
|
+
db.exec(
|
|
89
|
+
'CREATE INDEX IF NOT EXISTS idx_messages_conversation ON conversation_messages(conversation_id)'
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
// --- Migrate existing sessions to conversations ---
|
|
96
|
+
{
|
|
97
|
+
name: 'conversations-migrate-existing-sessions',
|
|
98
|
+
up(db) { migrateExistingSessionsToConversations(db); },
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
// --- Conversations: claude_session_id ---
|
|
102
|
+
{
|
|
103
|
+
name: 'conversations-add-claude_session_id',
|
|
104
|
+
up(db) { addColumnIfMissing(db, TABLE_CONVERSATIONS, 'claude_session_id', 'TEXT'); },
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
// --- Conversations token usage ---
|
|
108
|
+
{
|
|
109
|
+
name: 'conversations-add-input_tokens',
|
|
110
|
+
up(db) { addColumnIfMissing(db, TABLE_CONVERSATIONS, 'input_tokens', COL_INTEGER_DEFAULT_0); },
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: 'conversations-add-output_tokens',
|
|
114
|
+
up(db) { addColumnIfMissing(db, TABLE_CONVERSATIONS, 'output_tokens', COL_INTEGER_DEFAULT_0); },
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'conversations-add-thinking_tokens',
|
|
118
|
+
up(db) { addColumnIfMissing(db, TABLE_CONVERSATIONS, 'thinking_tokens', COL_INTEGER_DEFAULT_0); },
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: 'conversations-add-cache_read_input_tokens',
|
|
122
|
+
up(db) { addColumnIfMissing(db, TABLE_CONVERSATIONS, 'cache_read_input_tokens', COL_INTEGER_DEFAULT_0); },
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'conversations-add-cache_creation_input_tokens',
|
|
126
|
+
up(db) { addColumnIfMissing(db, TABLE_CONVERSATIONS, 'cache_creation_input_tokens', COL_INTEGER_DEFAULT_0); },
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'conversations-add-web_search_requests',
|
|
130
|
+
up(db) { addColumnIfMissing(db, TABLE_CONVERSATIONS, 'web_search_requests', COL_INTEGER_DEFAULT_0); },
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: 'conversations-add-context_window',
|
|
134
|
+
up(db) { addColumnIfMissing(db, TABLE_CONVERSATIONS, 'context_window', 'INTEGER DEFAULT 200000'); },
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: 'conversations-add-model',
|
|
138
|
+
up(db) { addColumnIfMissing(db, TABLE_CONVERSATIONS, 'model', 'TEXT'); },
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
// --- Conversation branching ---
|
|
142
|
+
{
|
|
143
|
+
name: 'conversations-add-parent_conversation_id',
|
|
144
|
+
up(db) {
|
|
145
|
+
const columns = getColumns(db, TABLE_CONVERSATIONS);
|
|
146
|
+
if (!columns.includes('parent_conversation_id')) {
|
|
147
|
+
db.exec(
|
|
148
|
+
'ALTER TABLE conversations ADD COLUMN parent_conversation_id TEXT REFERENCES conversations(id) ON DELETE SET NULL'
|
|
149
|
+
);
|
|
150
|
+
db.exec(
|
|
151
|
+
'CREATE INDEX IF NOT EXISTS idx_conversations_parent ON conversations(parent_conversation_id)'
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: 'conversations-add-branch_from_message_id',
|
|
158
|
+
up(db) {
|
|
159
|
+
addColumnIfMissing(
|
|
160
|
+
db, TABLE_CONVERSATIONS, 'branch_from_message_id',
|
|
161
|
+
'TEXT REFERENCES conversation_messages(id) ON DELETE SET NULL'
|
|
162
|
+
);
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
// --- Session todos: conversation_id ---
|
|
167
|
+
{
|
|
168
|
+
name: 'session_todos-add-conversation_id',
|
|
169
|
+
up(db) {
|
|
170
|
+
const columns = getColumns(db, 'session_todos');
|
|
171
|
+
if (!columns.includes('conversation_id')) {
|
|
172
|
+
db.exec(
|
|
173
|
+
'ALTER TABLE session_todos ADD COLUMN conversation_id TEXT REFERENCES conversations(id) ON DELETE CASCADE'
|
|
174
|
+
);
|
|
175
|
+
db.exec(
|
|
176
|
+
'CREATE INDEX IF NOT EXISTS idx_todos_conversation ON session_todos(conversation_id)'
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
// --- Conversation messages: model ---
|
|
183
|
+
{
|
|
184
|
+
name: 'conversation_messages-add-model',
|
|
185
|
+
up(db) { addColumnIfMissing(db, 'conversation_messages', 'model', 'TEXT'); },
|
|
186
|
+
},
|
|
187
|
+
];
|