circuschief 0.7.0 → 1.0.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/adapters/CodexAdapter.js +5 -4
- package/packages/server/src/api/canvas.js +22 -57
- package/packages/server/src/api/commandButtons.js +16 -15
- package/packages/server/src/api/index.js +2 -0
- package/packages/server/src/api/kanban.js +4 -2
- package/packages/server/src/api/projects-commandButtons.js +6 -6
- package/packages/server/src/api/projects-helpers.js +20 -3
- 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 +57 -5
- package/packages/server/src/api/projects-templates.js +38 -0
- package/packages/server/src/api/projects.js +28 -170
- package/packages/server/src/api/providers.js +11 -1
- package/packages/server/src/api/sessions-commands.js +46 -25
- package/packages/server/src/api/sessions-lifecycle.js +10 -10
- package/packages/server/src/api/sessions-patch.js +45 -1
- package/packages/server/src/api/sessions.js +6 -5
- package/packages/server/src/database.js +0 -2
- package/packages/server/src/db/DatabaseManager.js +5 -1
- package/packages/server/src/db/ProjectDefaultsRepository.js +3 -3
- package/packages/server/src/db/ProviderRepository.js +87 -32
- package/packages/server/src/db/SessionRepository.js +2 -1
- package/packages/server/src/db/SessionTemplateRepository.js +23 -2
- package/packages/server/src/db/index.js +0 -3
- package/packages/server/src/db/migrations/index.js +60 -7
- package/packages/server/src/db/migrations/kanbanMigrations.js +1 -1
- package/packages/server/src/db/migrations/miscMigrations.js +59 -184
- package/packages/server/src/db/migrations/projectsMigrations.js +31 -0
- package/packages/server/src/db/migrations/providerCommitAttributionMigrations.js +30 -0
- package/packages/server/src/db/migrations/providerMigrations.js +165 -0
- package/packages/server/src/db/migrations/sessionTableRecreate.js +136 -0
- package/packages/server/src/db/migrations/sessionsMigrations.js +18 -5
- package/packages/server/src/db/seedBaselineData.js +137 -0
- package/packages/server/src/db/session-helpers.js +32 -4
- package/packages/server/src/middleware/sessionLookup.js +81 -8
- package/packages/server/src/schema.sql +153 -132
- package/packages/server/src/scripts/backupDatabase.js +21 -0
- package/packages/server/src/scripts/dbUtils.js +81 -0
- package/packages/server/src/scripts/inspectDatabaseSchema.js +81 -0
- package/packages/server/src/scripts/validateDatabaseBaseline.js +212 -0
- package/packages/server/src/services/codexSpawnHelper.js +9 -0
- package/packages/server/src/services/commandButtonPrompts.js +14 -12
- package/packages/server/src/services/commandRunner.js +7 -1
- package/packages/server/src/services/e2eSpawnCapture.js +147 -0
- package/packages/server/src/services/gitCommitAttribution.js +150 -0
- package/packages/server/src/services/gitDiff.js +132 -0
- package/packages/server/src/services/gitRepoUrl.js +174 -0
- package/packages/server/src/services/gitService.js +48 -311
- package/packages/server/src/services/gitSessionSetup.js +11 -1
- package/packages/server/src/services/gitWorktree.js +127 -0
- package/packages/server/src/services/kanbanTriggers.js +6 -3
- package/packages/server/src/services/nodeSpawnHelper.js +9 -0
- package/packages/server/src/services/prUrlService.js +3 -3
- package/packages/server/src/services/queryParamBuilder.js +90 -0
- package/packages/server/src/services/sessionDuplicator.js +1 -5
- package/packages/server/src/services/sessionExecution.js +56 -108
- package/packages/server/src/services/sessionPrompts.js +12 -47
- package/packages/server/src/services/sessionProvider.js +10 -0
- package/packages/server/src/services/summaryService.js +5 -3
- package/packages/server/src/services/summaryStaleCheck.js +23 -4
- package/packages/server/src/services/templateTriggerService.js +3 -1
- package/packages/shared/src/constants.js +3 -0
- package/packages/shared/src/contracts/commandButtons.js +16 -2
- package/packages/shared/src/contracts/projects.js +2 -2
- package/packages/shared/src/contracts/providers.js +60 -0
- package/packages/shared/src/contracts/sessions.js +29 -2
- package/packages/shared/src/contracts/templates.js +12 -2
- package/packages/shared/src/types.js +1 -9
- package/packages/shared/src/utils.js +2 -2
- package/packages/web/dist/assets/{ActiveSessionsView-UJsCILDL.js → ActiveSessionsView-Cxh8mHmB.js} +1 -1
- package/packages/web/dist/assets/{AgentLogsView-BGFPLjLa.js → AgentLogsView-xdfI2bR6.js} +2 -2
- package/packages/web/dist/assets/ApiClient-DfbJwzpz.js +1 -0
- package/packages/web/dist/assets/ArchiveConfirmModal-DXZYdzHR.js +1 -0
- package/packages/web/dist/assets/ArchiveConfirmModal-DeoCVGXt.css +1 -0
- package/packages/web/dist/assets/CommandButtonDetailView-D8xfqLAp.js +1 -0
- package/packages/web/dist/assets/CommandButtonDetailView-D9zjx9ME.css +1 -0
- package/packages/web/dist/assets/EffortLevelSelector-D2Hdzc_8.js +1 -0
- package/packages/web/dist/assets/{GeneralSettingsView-DsHChEhv.js → GeneralSettingsView-sPXkLlLy.js} +1 -1
- package/packages/web/dist/assets/InputWithButton-B-o0DgMH.js +1 -0
- package/packages/web/dist/assets/{InputWithButton-cYdrEmTs.css → InputWithButton-D9HMvfR5.css} +1 -1
- package/packages/web/dist/assets/{InterpolationHelp-CIkOSkWX.js → InterpolationHelp-Dxn1li4l.js} +1 -1
- package/packages/web/dist/assets/MarkdownEditor-D4Kbb-9l.js +2 -0
- package/packages/web/dist/assets/ModelSelector-72C7MUH4.js +1 -0
- package/packages/web/dist/assets/{ModelSelector-D8hbTRIt.css → ModelSelector-BNYKujL-.css} +1 -1
- package/packages/web/dist/assets/NewSessionView-BR_COfgW.js +3 -0
- package/packages/web/dist/assets/NewSessionView-DBl7T2Xp.css +1 -0
- package/packages/web/dist/assets/ProjectEditView-DbqTbA0q.css +1 -0
- package/packages/web/dist/assets/ProjectEditView-WImU7sNd.js +1 -0
- package/packages/web/dist/assets/{ProjectListView-B9FuWESY.js → ProjectListView-CYmmAcBD.js} +1 -1
- package/packages/web/dist/assets/{ProjectNewView-D62jYlBL.js → ProjectNewView-DEhqw3Jv.js} +1 -1
- package/packages/web/dist/assets/ProvidersView-XZh3jkmH.js +1 -0
- package/packages/web/dist/assets/ProvidersView-bZemq_Rv.css +1 -0
- package/packages/web/dist/assets/QuickResponsesPanel-BqmnTd-D.js +1 -0
- package/packages/web/dist/assets/QuickResponsesPanel-dk-Rj8xx.css +1 -0
- package/packages/web/dist/assets/ResizableTextarea-BQNw5e0C.css +1 -0
- package/packages/web/dist/assets/ResizableTextarea-DpWdIAP6.js +1 -0
- package/packages/web/dist/assets/SessionCard-Bw77-KwD.js +1 -0
- package/packages/web/dist/assets/SessionDetailView-B59TEkr-.js +36 -0
- package/packages/web/dist/assets/SessionDetailView-CKVBnR4T.css +1 -0
- package/packages/web/dist/assets/{SessionFormOptions-DYUISplS.js → SessionFormOptions-hqijxc0S.js} +1 -1
- package/packages/web/dist/assets/SessionListView-3-xx6EVs.css +1 -0
- package/packages/web/dist/assets/SessionListView-DYXHM9I-.js +1 -0
- package/packages/web/dist/assets/{SessionLogStream-DpUE6Xsh.js → SessionLogStream-5NfVr9pF.js} +6 -6
- package/packages/web/dist/assets/{SettingsView-BC055tIA.js → SettingsView-DI8ncOAV.js} +1 -1
- package/packages/web/dist/assets/{SlashCommandWizard-DmTyNG9O.js → SlashCommandWizard-BQ_rMzn-.js} +1 -1
- package/packages/web/dist/assets/{SlashCommandWizard-Dn7sNaBd.css → SlashCommandWizard-DJzw3LP5.css} +1 -1
- package/packages/web/dist/assets/{SummarySettingsView-BgnRCwlq.js → SummarySettingsView-C2Qs35mm.js} +1 -1
- package/packages/web/dist/assets/TemplateDetailView-B5NI2oTR.css +1 -0
- package/packages/web/dist/assets/TemplateDetailView-zVkIvgtu.js +1 -0
- package/packages/web/dist/assets/{commandButtons-D4RPpLiu.js → commandButtons-CoU3G4zK.js} +1 -1
- package/packages/web/dist/assets/index-9yF1uCCA.js +1 -0
- package/packages/web/dist/assets/index-BKstCaYU.js +1 -0
- package/packages/web/dist/assets/index-BhbH7eOk.js +1 -0
- package/packages/web/dist/assets/{index-BGwH4Cfn.js → index-BjuRttEY.js} +3 -3
- package/packages/web/dist/assets/index-Bo7PdwM5.js +1 -0
- package/packages/web/dist/assets/index-C2QFVD7d.js +83 -0
- package/packages/web/dist/assets/index-C7Ww2auW.js +1 -0
- package/packages/web/dist/assets/index-CAGdsDh7.js +1 -0
- package/packages/web/dist/assets/index-CLRsVASf.js +3 -0
- package/packages/web/dist/assets/{index-Bn5xdGFM.js → index-CP-SxOlV.js} +1 -1
- package/packages/web/dist/assets/index-CslU0psO.js +1 -0
- package/packages/web/dist/assets/index-DI4NxaWD.js +1 -0
- package/packages/web/dist/assets/index-DOzONENy.js +1 -0
- package/packages/web/dist/assets/index-DUa7adFh.js +1 -0
- package/packages/web/dist/assets/index-DZBpETI5.js +1 -0
- package/packages/web/dist/assets/index-DsjWqc6R.js +7 -0
- package/packages/web/dist/assets/index-c99Bo3JV.js +1 -0
- package/packages/web/dist/assets/index-mT1JpxDc.js +1 -0
- package/packages/web/dist/assets/index-rkQx2tso.js +1 -0
- package/packages/web/dist/assets/{index-Cs2nxhrT.css → index-uySCcnA_.css} +1 -1
- package/packages/web/dist/assets/projectDefaults-B8esIcYq.js +1 -0
- package/packages/web/dist/assets/{projects-BUiOGmmb.js → projects-C-8PSxKi.js} +1 -1
- package/packages/web/dist/assets/{providers-Bh1ZiiJi.js → providers-oXifvvqN.js} +1 -1
- package/packages/web/dist/assets/sessions-Nq5VafSf.js +1 -0
- package/packages/web/dist/assets/{settings-Z4AVVmkJ.js → settings-DtpuiyT6.js} +1 -1
- package/packages/web/dist/index.html +2 -2
- package/packages/server/src/api/sessions-notes.js +0 -51
- package/packages/server/src/db/SessionNoteRepository.js +0 -60
- package/packages/web/dist/assets/ApiClient-B4YTtyY4.js +0 -1
- package/packages/web/dist/assets/ArchiveConfirmModal-BQ-4gI0R.css +0 -1
- package/packages/web/dist/assets/ArchiveConfirmModal-OFaj_uX5.js +0 -1
- package/packages/web/dist/assets/CommandButtonDetailView-D8S258uP.js +0 -1
- package/packages/web/dist/assets/CommandButtonDetailView-DBm3rzhw.css +0 -1
- package/packages/web/dist/assets/EffortLevelSelector-C2378L8e.js +0 -1
- package/packages/web/dist/assets/InputWithButton-Ci15ox0a.js +0 -1
- package/packages/web/dist/assets/MarkdownEditor-5-bexzUT.js +0 -2
- package/packages/web/dist/assets/ModelSelector-BMpR0DPr.js +0 -1
- package/packages/web/dist/assets/NewSessionView-BCqtIgWH.js +0 -3
- package/packages/web/dist/assets/NewSessionView-CUUdHkfv.css +0 -1
- package/packages/web/dist/assets/ProjectEditView-D9sK0fdH.css +0 -1
- package/packages/web/dist/assets/ProjectEditView-RFaxHhAX.js +0 -1
- package/packages/web/dist/assets/ProvidersView-DDKMIQWZ.js +0 -1
- package/packages/web/dist/assets/ProvidersView-DE82G_5W.css +0 -1
- package/packages/web/dist/assets/QuickResponseSettings-B8188A1D.css +0 -1
- package/packages/web/dist/assets/QuickResponseSettings-CDm5vwP7.js +0 -1
- package/packages/web/dist/assets/QuickResponsesPanel-BlFDvnZ2.css +0 -1
- package/packages/web/dist/assets/QuickResponsesPanel-DZ_Lre_l.js +0 -1
- package/packages/web/dist/assets/ResizableTextarea-DiIOEGjN.js +0 -1
- package/packages/web/dist/assets/ResizableTextarea-DsU3TVwF.css +0 -1
- package/packages/web/dist/assets/SessionCard-DmjnVYWn.js +0 -1
- package/packages/web/dist/assets/SessionDetailView-CL7nmfiB.js +0 -36
- package/packages/web/dist/assets/SessionDetailView-CupIkI7u.css +0 -1
- package/packages/web/dist/assets/SessionListView-BcxGz4aC.js +0 -1
- package/packages/web/dist/assets/SessionListView-fHlQyecX.css +0 -1
- package/packages/web/dist/assets/TemplateDetailView-BlhOmLUX.js +0 -1
- package/packages/web/dist/assets/TemplateDetailView-DT2m06W7.css +0 -1
- package/packages/web/dist/assets/index-4rhEeO0B.js +0 -1
- package/packages/web/dist/assets/index-9vb2KaAd.js +0 -1
- package/packages/web/dist/assets/index-B0CvZXuN.js +0 -7
- package/packages/web/dist/assets/index-B6G18FqB.js +0 -82
- package/packages/web/dist/assets/index-BUhvkAdF.js +0 -1
- package/packages/web/dist/assets/index-BcnkUk2o.js +0 -1
- package/packages/web/dist/assets/index-CNwkdB0T.js +0 -1
- package/packages/web/dist/assets/index-CfL84oGW.js +0 -1
- package/packages/web/dist/assets/index-CkmxO8Mm.js +0 -1
- package/packages/web/dist/assets/index-Cpy4-yv3.js +0 -1
- package/packages/web/dist/assets/index-CrAQJmoZ.js +0 -1
- package/packages/web/dist/assets/index-D6Ky9vJe.js +0 -3
- package/packages/web/dist/assets/index-DfrE0gAC.js +0 -1
- package/packages/web/dist/assets/index-KwEyz0F3.js +0 -1
- package/packages/web/dist/assets/index-OfCywayk.js +0 -1
- package/packages/web/dist/assets/index-PDesaJc6.js +0 -1
- package/packages/web/dist/assets/index-uB6nhSvz.js +0 -1
- package/packages/web/dist/assets/sessions-DH1R-NhV.js +0 -1
package/package.json
CHANGED
|
@@ -14,16 +14,17 @@ let codexCliUnavailable = false;
|
|
|
14
14
|
*
|
|
15
15
|
* Two execution paths:
|
|
16
16
|
*
|
|
17
|
-
* 1. CLI path (default) — spawns
|
|
17
|
+
* 1. CLI path (default) — spawns `codex exec --json ...` and parses its
|
|
18
18
|
* line-delimited JSON stdout. Uses {@link createCodexEventMapper} to
|
|
19
19
|
* normalize events into the SDK-shaped envelope the rest of the app
|
|
20
20
|
* already understands.
|
|
21
21
|
*
|
|
22
22
|
* 2. Direct-API path — activated by {@code USE_CODEX_DIRECT_API=1}. Uses
|
|
23
23
|
* the official {@code openai} SDK with Chat Completions streaming
|
|
24
|
-
* against the provider's configured baseURL/apiKey.
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* against the provider's configured baseURL/apiKey. It bypasses
|
|
25
|
+
* CLI-specific behavior such as sandbox enforcement and Codex
|
|
26
|
+
* commit-attribution config. Intended for
|
|
27
|
+
* environments where the Codex CLI isn't installable.
|
|
27
28
|
*
|
|
28
29
|
* Capabilities in v1:
|
|
29
30
|
* - streaming: true
|
|
@@ -2,10 +2,11 @@ import { Router } from 'express';
|
|
|
2
2
|
import { readFileSync, existsSync, statSync } from 'fs';
|
|
3
3
|
import { mkdir } from 'fs/promises';
|
|
4
4
|
import { extname, join, basename } from 'path';
|
|
5
|
-
import {
|
|
5
|
+
import { canvasItems } from '../database.js';
|
|
6
6
|
import { broadcastToSession } from '../websocket.js';
|
|
7
7
|
import { WS_MESSAGE_TYPES, UpdateCanvasItemRequest } from '../../../shared/src/index.js';
|
|
8
8
|
import { upload, handleUploadError } from '../middleware/upload.js';
|
|
9
|
+
import { requireRootSessionAndProject } from '../middleware/sessionLookup.js';
|
|
9
10
|
import {
|
|
10
11
|
isBinaryContent,
|
|
11
12
|
getTypeFromExtension,
|
|
@@ -19,8 +20,6 @@ import {
|
|
|
19
20
|
import trashRoutes from './canvas-trash-routes.js';
|
|
20
21
|
|
|
21
22
|
// Error message constants
|
|
22
|
-
const ERR_SESSION_NOT_FOUND = 'Session not found';
|
|
23
|
-
|
|
24
23
|
/**
|
|
25
24
|
* Process multipart file upload (Mode 1)
|
|
26
25
|
* @param {Express.Multer.File} file - The uploaded file from multer
|
|
@@ -84,12 +83,7 @@ router.use('/', trashRoutes);
|
|
|
84
83
|
// 1. Multipart mode: FormData with 'file' field - from browser file uploads
|
|
85
84
|
// 2. File mode: { filePath } - reads file from disk
|
|
86
85
|
// 3. Inline mode: { type, content, filename } - uses provided content directly
|
|
87
|
-
router.post('/:id/canvas', upload.single('file'), handleUploadError, (req, res) => {
|
|
88
|
-
const session = sessions.getById(req.params.id);
|
|
89
|
-
if (!session) {
|
|
90
|
-
return res.status(404).json({ error: ERR_SESSION_NOT_FOUND });
|
|
91
|
-
}
|
|
92
|
-
|
|
86
|
+
router.post('/:id/canvas', requireRootSessionAndProject, upload.single('file'), handleUploadError, (req, res) => {
|
|
93
87
|
const { filePath, type, content, filename } = req.body;
|
|
94
88
|
let result;
|
|
95
89
|
|
|
@@ -110,21 +104,16 @@ router.post('/:id/canvas', upload.single('file'), handleUploadError, (req, res)
|
|
|
110
104
|
return res.status(400).json({ error: result.error });
|
|
111
105
|
}
|
|
112
106
|
|
|
113
|
-
const item = canvasItems.create(req.
|
|
107
|
+
const item = canvasItems.create(req.rootSessionId, result.itemData);
|
|
114
108
|
|
|
115
109
|
// Broadcast to session subscribers
|
|
116
|
-
broadcastCanvasUpdate(req.
|
|
110
|
+
broadcastCanvasUpdate(req.rootSessionId, item);
|
|
117
111
|
|
|
118
112
|
res.status(201).json(item);
|
|
119
113
|
});
|
|
120
114
|
|
|
121
115
|
// PUT /api/sessions/:id/canvas/:itemId - Update canvas item content in-place
|
|
122
|
-
router.put('/:id/canvas/:itemId', (req, res) => {
|
|
123
|
-
const session = sessions.getById(req.params.id);
|
|
124
|
-
if (!session) {
|
|
125
|
-
return res.status(404).json({ error: ERR_SESSION_NOT_FOUND });
|
|
126
|
-
}
|
|
127
|
-
|
|
116
|
+
router.put('/:id/canvas/:itemId', requireRootSessionAndProject, (req, res) => {
|
|
128
117
|
const parsed = UpdateCanvasItemRequest.safeParse(req.body);
|
|
129
118
|
if (!parsed.success) {
|
|
130
119
|
return res.status(400).json({ error: 'Invalid request body', details: parsed.error.issues });
|
|
@@ -135,7 +124,7 @@ router.put('/:id/canvas/:itemId', (req, res) => {
|
|
|
135
124
|
return res.status(404).json({ error: 'Canvas item not found' });
|
|
136
125
|
}
|
|
137
126
|
|
|
138
|
-
if (item.sessionId !== req.
|
|
127
|
+
if (item.sessionId !== req.rootSessionId) {
|
|
139
128
|
return res.status(400).json({ error: 'Canvas item does not belong to this session' });
|
|
140
129
|
}
|
|
141
130
|
|
|
@@ -144,20 +133,15 @@ router.put('/:id/canvas/:itemId', (req, res) => {
|
|
|
144
133
|
}
|
|
145
134
|
|
|
146
135
|
const updatedItem = canvasItems.updateContent(req.params.itemId, parsed.data.content);
|
|
147
|
-
broadcastToSession(req.
|
|
136
|
+
broadcastToSession(req.rootSessionId, WS_MESSAGE_TYPES.CANVAS_UPDATE, { item: updatedItem });
|
|
148
137
|
res.json(updatedItem);
|
|
149
138
|
});
|
|
150
139
|
|
|
151
140
|
// GET /api/sessions/:id/canvas - List canvas items
|
|
152
141
|
// Returns only latest version of each file (no version metadata exposed)
|
|
153
|
-
router.get('/:id/canvas', (req, res) => {
|
|
154
|
-
const session = sessions.getById(req.params.id);
|
|
155
|
-
if (!session) {
|
|
156
|
-
return res.status(404).json({ error: ERR_SESSION_NOT_FOUND });
|
|
157
|
-
}
|
|
158
|
-
|
|
142
|
+
router.get('/:id/canvas', requireRootSessionAndProject, (req, res) => {
|
|
159
143
|
// Get only latest versions (one per filename)
|
|
160
|
-
const items = canvasItems.getLatestVersionsBySessionId(req.
|
|
144
|
+
const items = canvasItems.getLatestVersionsBySessionId(req.rootSessionId);
|
|
161
145
|
|
|
162
146
|
// Strip content/data from list responses to reduce payload size.
|
|
163
147
|
// Clients should use GET /canvas/file/:filename/content for inline content.
|
|
@@ -166,14 +150,9 @@ router.get('/:id/canvas', (req, res) => {
|
|
|
166
150
|
|
|
167
151
|
// GET /api/sessions/:id/canvas/all - List ALL canvas items (including all versions)
|
|
168
152
|
// Returns all versions of each file for the frontend UI
|
|
169
|
-
router.get('/:id/canvas/all', (req, res) => {
|
|
170
|
-
const session = sessions.getById(req.params.id);
|
|
171
|
-
if (!session) {
|
|
172
|
-
return res.status(404).json({ error: ERR_SESSION_NOT_FOUND });
|
|
173
|
-
}
|
|
174
|
-
|
|
153
|
+
router.get('/:id/canvas/all', requireRootSessionAndProject, (req, res) => {
|
|
175
154
|
// Get ALL versions (not just latest)
|
|
176
|
-
const items = canvasItems.getBySessionId(req.
|
|
155
|
+
const items = canvasItems.getBySessionId(req.rootSessionId);
|
|
177
156
|
|
|
178
157
|
// Strip content/data from list responses to reduce payload size.
|
|
179
158
|
res.json(items.map(({ content: _content, data: _data, ...meta }) => meta));
|
|
@@ -182,15 +161,11 @@ router.get('/:id/canvas/all', (req, res) => {
|
|
|
182
161
|
// GET /api/sessions/:id/canvas/file/:filename/history/:version - Get historical version of canvas file
|
|
183
162
|
// Uses standard versioning: version 1 = oldest, version N = latest (where N = totalVersions)
|
|
184
163
|
// NOTE: This route must be defined BEFORE the main file route to match correctly
|
|
185
|
-
router.get('/:id/canvas/file/:filename/history/:version', async (req, res) => {
|
|
186
|
-
const {
|
|
164
|
+
router.get('/:id/canvas/file/:filename/history/:version', requireRootSessionAndProject, async (req, res) => {
|
|
165
|
+
const { filename, version } = req.params;
|
|
166
|
+
const sessionId = req.rootSessionId;
|
|
187
167
|
const versionNum = parseInt(version, 10);
|
|
188
168
|
|
|
189
|
-
const session = sessions.getById(sessionId);
|
|
190
|
-
if (!session) {
|
|
191
|
-
return res.status(404).json({ error: ERR_SESSION_NOT_FOUND });
|
|
192
|
-
}
|
|
193
|
-
|
|
194
169
|
// Get all versions of this file (newest first)
|
|
195
170
|
const allVersions = canvasItems.getAllVersionsByFilename(sessionId, filename);
|
|
196
171
|
if (allVersions.length === 0) {
|
|
@@ -247,11 +222,8 @@ router.get('/:id/canvas/file/:filename/history/:version', async (req, res) => {
|
|
|
247
222
|
// GET /api/sessions/:id/canvas/file/:filename/content - Get canvas file content inline
|
|
248
223
|
// Returns content/data inline in JSON for browser consumption (unlike the temp-file endpoint below)
|
|
249
224
|
// Supports ?version=N query param (1-based, 1 = oldest)
|
|
250
|
-
router.get('/:id/canvas/file/:filename/content', (req, res) => {
|
|
251
|
-
const
|
|
252
|
-
if (!session) return res.status(404).json({ error: ERR_SESSION_NOT_FOUND });
|
|
253
|
-
|
|
254
|
-
const allVersions = canvasItems.getAllVersionsByFilename(req.params.id, req.params.filename);
|
|
225
|
+
router.get('/:id/canvas/file/:filename/content', requireRootSessionAndProject, (req, res) => {
|
|
226
|
+
const allVersions = canvasItems.getAllVersionsByFilename(req.rootSessionId, req.params.filename);
|
|
255
227
|
if (allVersions.length === 0) return res.status(404).json({ error: 'File not found' });
|
|
256
228
|
|
|
257
229
|
// Support ?version=N (1-based, 1 = oldest)
|
|
@@ -280,13 +252,10 @@ router.get('/:id/canvas/file/:filename/content', (req, res) => {
|
|
|
280
252
|
});
|
|
281
253
|
|
|
282
254
|
// GET /api/sessions/:id/canvas/:itemId/content - Get one canvas item content inline
|
|
283
|
-
router.get('/:id/canvas/:itemId/content', (req, res) => {
|
|
284
|
-
const session = sessions.getById(req.params.id);
|
|
285
|
-
if (!session) return res.status(404).json({ error: ERR_SESSION_NOT_FOUND });
|
|
286
|
-
|
|
255
|
+
router.get('/:id/canvas/:itemId/content', requireRootSessionAndProject, (req, res) => {
|
|
287
256
|
const item = canvasItems.getById(req.params.itemId);
|
|
288
257
|
if (!item) return res.status(404).json({ error: 'Canvas item not found' });
|
|
289
|
-
if (item.sessionId !== req.
|
|
258
|
+
if (item.sessionId !== req.rootSessionId) {
|
|
290
259
|
return res.status(400).json({ error: 'Canvas item does not belong to this session' });
|
|
291
260
|
}
|
|
292
261
|
|
|
@@ -302,13 +271,9 @@ router.get('/:id/canvas/:itemId/content', (req, res) => {
|
|
|
302
271
|
// GET /api/sessions/:id/canvas/file/:filename - Get canvas file by filename
|
|
303
272
|
// Writes the file to /tmp and returns the file path for Claude's Read tool
|
|
304
273
|
// Always returns the latest version
|
|
305
|
-
router.get('/:id/canvas/file/:filename', async (req, res) => {
|
|
306
|
-
const {
|
|
307
|
-
|
|
308
|
-
const session = sessions.getById(sessionId);
|
|
309
|
-
if (!session) {
|
|
310
|
-
return res.status(404).json({ error: ERR_SESSION_NOT_FOUND });
|
|
311
|
-
}
|
|
274
|
+
router.get('/:id/canvas/file/:filename', requireRootSessionAndProject, async (req, res) => {
|
|
275
|
+
const { filename } = req.params;
|
|
276
|
+
const sessionId = req.rootSessionId;
|
|
312
277
|
|
|
313
278
|
// Get all versions of this file (newest first)
|
|
314
279
|
const allVersions = canvasItems.getAllVersionsByFilename(sessionId, filename);
|
|
@@ -8,6 +8,7 @@ import { databaseManager } from '../db/DatabaseManager.js';
|
|
|
8
8
|
|
|
9
9
|
// Error message constants
|
|
10
10
|
const ERR_SESSION_NOT_FOUND = 'Session not found';
|
|
11
|
+
const ERR_BUTTON_NOT_FOUND = 'Circus Command not found';
|
|
11
12
|
|
|
12
13
|
const router = Router({ mergeParams: true });
|
|
13
14
|
|
|
@@ -64,14 +65,14 @@ function broadcastCommandRunError({ sessionId, projectId, runId, buttonId, error
|
|
|
64
65
|
});
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
// GET /api/projects/:projectId/
|
|
68
|
+
// GET /api/projects/:projectId/circus-commands - List all command buttons for project
|
|
68
69
|
router.get('/', (req, res) => {
|
|
69
70
|
const { projectId } = req.params;
|
|
70
71
|
const buttons = commandButtons.getByProjectId(projectId);
|
|
71
72
|
res.json(buttons);
|
|
72
73
|
});
|
|
73
74
|
|
|
74
|
-
// GET /api/projects/:projectId/
|
|
75
|
+
// GET /api/projects/:projectId/circus-commands/latest-runs - Get latest run for each button per session in project
|
|
75
76
|
router.get('/latest-runs', (req, res) => {
|
|
76
77
|
const { projectId } = req.params;
|
|
77
78
|
|
|
@@ -85,7 +86,7 @@ router.get('/latest-runs', (req, res) => {
|
|
|
85
86
|
res.json(latestRuns);
|
|
86
87
|
});
|
|
87
88
|
|
|
88
|
-
// POST /api/projects/:projectId/
|
|
89
|
+
// POST /api/projects/:projectId/circus-commands - Create new command button
|
|
89
90
|
router.post('/', (req, res) => {
|
|
90
91
|
const result = CreateCommandButtonRequest.safeParse(req.body);
|
|
91
92
|
if (!result.success) {
|
|
@@ -103,20 +104,20 @@ router.post('/', (req, res) => {
|
|
|
103
104
|
res.status(201).json(button);
|
|
104
105
|
});
|
|
105
106
|
|
|
106
|
-
// GET /api/projects/:projectId/
|
|
107
|
+
// GET /api/projects/:projectId/circus-commands/:id - Get single button
|
|
107
108
|
router.get('/:id', (req, res) => {
|
|
108
109
|
const button = commandButtons.getById(req.params.id);
|
|
109
110
|
if (!button) {
|
|
110
|
-
return res.status(404).json({ error:
|
|
111
|
+
return res.status(404).json({ error: ERR_BUTTON_NOT_FOUND });
|
|
111
112
|
}
|
|
112
113
|
res.json(button);
|
|
113
114
|
});
|
|
114
115
|
|
|
115
|
-
// PATCH /api/projects/:projectId/
|
|
116
|
+
// PATCH /api/projects/:projectId/circus-commands/:id - Update button
|
|
116
117
|
router.patch('/:id', (req, res) => {
|
|
117
118
|
const button = commandButtons.getById(req.params.id);
|
|
118
119
|
if (!button) {
|
|
119
|
-
return res.status(404).json({ error:
|
|
120
|
+
return res.status(404).json({ error: ERR_BUTTON_NOT_FOUND });
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
const result = UpdateCommandButtonRequest.safeParse(req.body);
|
|
@@ -135,18 +136,18 @@ router.patch('/:id', (req, res) => {
|
|
|
135
136
|
res.json(updated);
|
|
136
137
|
});
|
|
137
138
|
|
|
138
|
-
// DELETE /api/projects/:projectId/
|
|
139
|
+
// DELETE /api/projects/:projectId/circus-commands/:id - Delete button
|
|
139
140
|
router.delete('/:id', (req, res) => {
|
|
140
141
|
const button = commandButtons.getById(req.params.id);
|
|
141
142
|
if (!button) {
|
|
142
|
-
return res.status(404).json({ error:
|
|
143
|
+
return res.status(404).json({ error: ERR_BUTTON_NOT_FOUND });
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
commandButtons.delete(req.params.id);
|
|
146
147
|
res.status(204).send();
|
|
147
148
|
});
|
|
148
149
|
|
|
149
|
-
// POST /api/sessions/:sessionId/
|
|
150
|
+
// POST /api/sessions/:sessionId/circus-commands/:buttonId/run - Execute button command
|
|
150
151
|
router.post('/run/:buttonId', (req, res) => {
|
|
151
152
|
const { sessionId, buttonId } = req.params;
|
|
152
153
|
|
|
@@ -157,7 +158,7 @@ router.post('/run/:buttonId', (req, res) => {
|
|
|
157
158
|
|
|
158
159
|
const button = commandButtons.getById(buttonId);
|
|
159
160
|
if (!button) {
|
|
160
|
-
return res.status(404).json({ error:
|
|
161
|
+
return res.status(404).json({ error: ERR_BUTTON_NOT_FOUND });
|
|
161
162
|
}
|
|
162
163
|
|
|
163
164
|
const workingDirectory = session.gitWorktree || session.project?.workingDirectory || process.cwd();
|
|
@@ -181,7 +182,7 @@ router.post('/run/:buttonId', (req, res) => {
|
|
|
181
182
|
})();
|
|
182
183
|
});
|
|
183
184
|
|
|
184
|
-
// GET /api/sessions/:sessionId/
|
|
185
|
+
// GET /api/sessions/:sessionId/circus-commands/runs - Get active runs for session
|
|
185
186
|
router.get('/runs', (req, res) => {
|
|
186
187
|
const { sessionId } = req.params;
|
|
187
188
|
|
|
@@ -221,7 +222,7 @@ router.get('/runs', (req, res) => {
|
|
|
221
222
|
res.json(Array.from(runMap.values()));
|
|
222
223
|
});
|
|
223
224
|
|
|
224
|
-
// GET /api/sessions/:sessionId/
|
|
225
|
+
// GET /api/sessions/:sessionId/circus-commands/runs/:runId - Get single run by ID
|
|
225
226
|
router.get('/runs/:runId', (req, res) => {
|
|
226
227
|
const { sessionId, runId } = req.params;
|
|
227
228
|
|
|
@@ -256,7 +257,7 @@ router.get('/runs/:runId', (req, res) => {
|
|
|
256
257
|
});
|
|
257
258
|
});
|
|
258
259
|
|
|
259
|
-
// DELETE /api/sessions/:sessionId/
|
|
260
|
+
// DELETE /api/sessions/:sessionId/circus-commands/runs/:runId - Delete a command run record
|
|
260
261
|
router.delete('/runs/:runId', (req, res) => {
|
|
261
262
|
const { sessionId, runId } = req.params;
|
|
262
263
|
|
|
@@ -292,7 +293,7 @@ router.delete('/runs/:runId', (req, res) => {
|
|
|
292
293
|
res.status(204).send();
|
|
293
294
|
});
|
|
294
295
|
|
|
295
|
-
// POST /api/sessions/:sessionId/
|
|
296
|
+
// POST /api/sessions/:sessionId/circus-commands/runs/:runId/kill - Kill running command
|
|
296
297
|
router.post('/runs/:runId/kill', (req, res) => {
|
|
297
298
|
const { sessionId, runId } = req.params;
|
|
298
299
|
|
|
@@ -14,6 +14,7 @@ import kanbanRouter from './kanban.js';
|
|
|
14
14
|
import agentsRouter from './agents.js';
|
|
15
15
|
import { getDbPath } from '../database.js';
|
|
16
16
|
import { schedulerService } from '../services/schedulerService.js';
|
|
17
|
+
import { isE2ESpawnCaptureEnabled } from '../services/e2eSpawnCapture.js';
|
|
17
18
|
|
|
18
19
|
const router = Router();
|
|
19
20
|
|
|
@@ -29,6 +30,7 @@ router.get('/server-info', (_req, res) => {
|
|
|
29
30
|
dbPath: getDbPath(),
|
|
30
31
|
vcrMode: vcr && vcr.length > 0 ? vcr : null,
|
|
31
32
|
schedulerRunning: schedulerService.isRunning(),
|
|
33
|
+
e2eSpawnCaptureEnabled: isE2ESpawnCaptureEnabled(),
|
|
32
34
|
});
|
|
33
35
|
});
|
|
34
36
|
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
ReorderKanbanCardsRequest,
|
|
12
12
|
} from '../../../shared/src/contracts/kanban.js';
|
|
13
13
|
import { moveCard as moveCardService } from '../services/kanbanService.js';
|
|
14
|
+
import { resolveBodyRootSessionForProject } from '../middleware/sessionLookup.js';
|
|
14
15
|
|
|
15
16
|
const router = Router({ mergeParams: true });
|
|
16
17
|
|
|
@@ -215,7 +216,7 @@ router.put('/lanes/reorder', (req, res) => {
|
|
|
215
216
|
* POST /api/projects/:projectId/kanban/cards
|
|
216
217
|
* Add a session to the board (create card in a lane)
|
|
217
218
|
*/
|
|
218
|
-
router.post('/cards', (req, res) => {
|
|
219
|
+
router.post('/cards', resolveBodyRootSessionForProject('projectId'), (req, res) => {
|
|
219
220
|
const { projectId } = req.params;
|
|
220
221
|
|
|
221
222
|
const result = CreateKanbanCardRequest.safeParse(req.body);
|
|
@@ -223,7 +224,8 @@ router.post('/cards', (req, res) => {
|
|
|
223
224
|
return res.status(400).json({ error: result.error.issues[0].message });
|
|
224
225
|
}
|
|
225
226
|
|
|
226
|
-
const {
|
|
227
|
+
const { laneId } = result.data;
|
|
228
|
+
const sessionId = req.bodyRootSessionId;
|
|
227
229
|
|
|
228
230
|
// Check if session already has a card
|
|
229
231
|
const existingCard = kanbanCards.getBySessionId(sessionId);
|
|
@@ -4,11 +4,11 @@ import { CreateCommandButtonRequest, UpdateCommandButtonRequest } from '../../..
|
|
|
4
4
|
|
|
5
5
|
// Error message constants
|
|
6
6
|
const ERR_PROJECT_NOT_FOUND = 'Project not found';
|
|
7
|
-
const ERR_BUTTON_NOT_FOUND = 'Command
|
|
7
|
+
const ERR_BUTTON_NOT_FOUND = 'Circus Command not found';
|
|
8
8
|
|
|
9
9
|
const router = Router({ mergeParams: true });
|
|
10
10
|
|
|
11
|
-
// GET /api/projects/:id/
|
|
11
|
+
// GET /api/projects/:id/circus-commands - List all command buttons for project
|
|
12
12
|
router.get('/', (req, res) => {
|
|
13
13
|
const project = projects.getById(req.params.id);
|
|
14
14
|
if (!project) {
|
|
@@ -19,7 +19,7 @@ router.get('/', (req, res) => {
|
|
|
19
19
|
res.json(buttons);
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
// POST /api/projects/:id/
|
|
22
|
+
// POST /api/projects/:id/circus-commands - Create new command button
|
|
23
23
|
router.post('/', (req, res) => {
|
|
24
24
|
const project = projects.getById(req.params.id);
|
|
25
25
|
if (!project) {
|
|
@@ -42,7 +42,7 @@ router.post('/', (req, res) => {
|
|
|
42
42
|
res.status(201).json(button);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
// GET /api/projects/:id/
|
|
45
|
+
// GET /api/projects/:id/circus-commands/:buttonId - Get single button
|
|
46
46
|
router.get('/:buttonId', (req, res) => {
|
|
47
47
|
const project = projects.getById(req.params.id);
|
|
48
48
|
if (!project) {
|
|
@@ -56,7 +56,7 @@ router.get('/:buttonId', (req, res) => {
|
|
|
56
56
|
res.json(button);
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
-
// PATCH /api/projects/:id/
|
|
59
|
+
// PATCH /api/projects/:id/circus-commands/:buttonId - Update button
|
|
60
60
|
router.patch('/:buttonId', (req, res) => {
|
|
61
61
|
const project = projects.getById(req.params.id);
|
|
62
62
|
if (!project) {
|
|
@@ -77,7 +77,7 @@ router.patch('/:buttonId', (req, res) => {
|
|
|
77
77
|
res.json(updated);
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
-
// DELETE /api/projects/:id/
|
|
80
|
+
// DELETE /api/projects/:id/circus-commands/:buttonId - Delete button
|
|
81
81
|
router.delete('/:buttonId', (req, res) => {
|
|
82
82
|
const project = projects.getById(req.params.id);
|
|
83
83
|
if (!project) {
|
|
@@ -1,22 +1,39 @@
|
|
|
1
|
+
import { generateWorktreeBranch } from '../../../shared/src/index.js';
|
|
1
2
|
import { isGitRepo } from '../services/gitService.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Validate and default git settings for git-backed projects.
|
|
5
6
|
* If gitMode or gitBranch are missing for a git project, defaults are applied
|
|
6
|
-
*
|
|
7
|
+
* instead of rejecting the request. Worktree sessions receive a generated branch
|
|
8
|
+
* so they never try to check out an already-active branch such as main.
|
|
9
|
+
* Current-mode sessions skip branch generation entirely.
|
|
7
10
|
* @param {Object} config - The session configuration
|
|
8
11
|
* @param {Object} project - The project object
|
|
9
12
|
* @returns {Promise<{config: Object, error: string|null}>} Updated config and error message if validation fails.
|
|
10
13
|
*/
|
|
11
14
|
export async function validateGitSettings(config, project) {
|
|
15
|
+
// Current mode: no branch needed, pass through as-is
|
|
16
|
+
if (config.gitMode === 'current') {
|
|
17
|
+
return {
|
|
18
|
+
config: {
|
|
19
|
+
...config,
|
|
20
|
+
gitBranch: null,
|
|
21
|
+
},
|
|
22
|
+
error: null,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
12
26
|
if (!config.gitMode || !config.gitBranch) {
|
|
13
27
|
const isGit = await isGitRepo(project.workingDirectory);
|
|
14
28
|
if (isGit) {
|
|
29
|
+
const gitMode = config.gitMode || 'none';
|
|
15
30
|
return {
|
|
16
31
|
config: {
|
|
17
32
|
...config,
|
|
18
|
-
gitMode
|
|
19
|
-
gitBranch: config.gitBranch || '
|
|
33
|
+
gitMode,
|
|
34
|
+
gitBranch: config.gitBranch || (gitMode === 'worktree'
|
|
35
|
+
? generateWorktreeBranch(config.name, config.prompt)
|
|
36
|
+
: 'main'),
|
|
20
37
|
},
|
|
21
38
|
error: null,
|
|
22
39
|
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { sessions, projectDefaults } from '../database.js';
|
|
2
|
+
import { ProjectDefaultsRepository } from '../db/ProjectDefaultsRepository.js';
|
|
3
|
+
import { broadcastToProject } from '../websocket.js';
|
|
4
|
+
import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
|
|
5
|
+
import {
|
|
6
|
+
generateInitialName,
|
|
7
|
+
prepareSessionConfig,
|
|
8
|
+
applyTemplateOverrides,
|
|
9
|
+
resolveNextTemplateId,
|
|
10
|
+
buildSchedulingUpdate,
|
|
11
|
+
setupAndStartSession,
|
|
12
|
+
} from './projects-session-helpers.js';
|
|
13
|
+
import { validateGitSettings } from './projects-helpers.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Validate and prepare the session configuration from the request body.
|
|
17
|
+
* Returns { config, nextTemplateId } on success, or { error, status } on failure.
|
|
18
|
+
*/
|
|
19
|
+
export async function validateAndPrepareSessionConfig(reqBody, reqFiles, projectId, project) {
|
|
20
|
+
const projectDefs = projectDefaults.getByProjectId(projectId);
|
|
21
|
+
const systemDefaults = ProjectDefaultsRepository.getSystemDefaults();
|
|
22
|
+
const config = prepareSessionConfig(reqBody, projectDefs, systemDefaults);
|
|
23
|
+
config.files = reqFiles || [];
|
|
24
|
+
|
|
25
|
+
if (!config.prompt) {
|
|
26
|
+
return { error: 'Prompt is required', status: 400 };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (config.schedulingError) {
|
|
30
|
+
return { error: config.schedulingError, status: 400 };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (config.parentSessionId) {
|
|
34
|
+
const parentSession = sessions.getById(config.parentSessionId);
|
|
35
|
+
if (!parentSession) {
|
|
36
|
+
return { error: 'Parent session not found', status: 404 };
|
|
37
|
+
}
|
|
38
|
+
if (parentSession.projectId !== projectId) {
|
|
39
|
+
return { error: 'Parent session does not belong to this project', status: 400 };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Apply template overrides and resolve nextTemplateId
|
|
44
|
+
applyTemplateOverrides(config);
|
|
45
|
+
const { nextTemplateId, error: nextTemplateError } = resolveNextTemplateId(reqBody, config.nextTemplateId || null);
|
|
46
|
+
if (nextTemplateError) {
|
|
47
|
+
return { error: nextTemplateError, status: 400 };
|
|
48
|
+
}
|
|
49
|
+
config.nextTemplateId = nextTemplateId;
|
|
50
|
+
|
|
51
|
+
// Validate git settings for git repos
|
|
52
|
+
const { config: updatedConfig, error: gitError } = await validateGitSettings(config, project);
|
|
53
|
+
if (gitError) {
|
|
54
|
+
return { error: gitError, status: 400 };
|
|
55
|
+
}
|
|
56
|
+
Object.assign(config, updatedConfig);
|
|
57
|
+
|
|
58
|
+
return { config, nextTemplateId };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create the session row and apply any post-create updates.
|
|
63
|
+
* Returns the created session (already persisted in DB).
|
|
64
|
+
*/
|
|
65
|
+
export function createSessionRow(projectId, config, nextTemplateId, initialStatus) {
|
|
66
|
+
const sessionName = config.name || generateInitialName(config.prompt);
|
|
67
|
+
const session = sessions.create(projectId, sessionName, config.prompt, {
|
|
68
|
+
mode: config.mode,
|
|
69
|
+
thinkingEnabled: config.thinkingEnabled,
|
|
70
|
+
gitBranch: config.gitBranch,
|
|
71
|
+
parentSessionId: config.parentSessionId,
|
|
72
|
+
status: initialStatus,
|
|
73
|
+
model: config.model,
|
|
74
|
+
providerId: config.providerId,
|
|
75
|
+
effortLevel: config.effortLevel,
|
|
76
|
+
agentType: config.agentType,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const postCreateUpdate = {
|
|
80
|
+
...(nextTemplateId ? { nextTemplateId } : {}),
|
|
81
|
+
...buildSchedulingUpdate(config, initialStatus),
|
|
82
|
+
};
|
|
83
|
+
if (Object.keys(postCreateUpdate).length > 0) {
|
|
84
|
+
sessions.update(session.id, postCreateUpdate);
|
|
85
|
+
}
|
|
86
|
+
return session;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Run setupAndStartSession and translate any failure into an error response,
|
|
91
|
+
* marking the session as errored and broadcasting the update.
|
|
92
|
+
*/
|
|
93
|
+
export async function startSessionOrFail(req, res, { session, config, project }) {
|
|
94
|
+
try {
|
|
95
|
+
const { updatedSession } = await setupAndStartSession({
|
|
96
|
+
session, config, project, projectId: req.params.id, files: config.files,
|
|
97
|
+
});
|
|
98
|
+
return res.status(201).json(updatedSession);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error('Git setup error:', error);
|
|
101
|
+
const updatedSession = sessions.update(session.id, { status: 'error', error: error.message });
|
|
102
|
+
broadcastToProject(req.params.id, WS_MESSAGE_TYPES.SESSION_UPDATED, {
|
|
103
|
+
projectId: req.params.id,
|
|
104
|
+
sessionId: session.id,
|
|
105
|
+
session: updatedSession,
|
|
106
|
+
});
|
|
107
|
+
return res.status(500).json({ error: `Git setup failed: ${error.message}` });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { projects, projectDefaults } from '../database.js';
|
|
3
|
+
import { ProjectSessionDefaultsRequest } from '../../../shared/src/contracts/projects.js';
|
|
4
|
+
|
|
5
|
+
const ERR_PROJECT_NOT_FOUND = 'Project not found';
|
|
6
|
+
|
|
7
|
+
const router = Router({ mergeParams: true });
|
|
8
|
+
|
|
9
|
+
// GET /api/projects/:id/session-defaults - Get session defaults for project
|
|
10
|
+
router.get('/', (req, res) => {
|
|
11
|
+
const project = projects.getById(req.params.id);
|
|
12
|
+
if (!project) {
|
|
13
|
+
return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const defaults = projectDefaults.getByProjectId(req.params.id);
|
|
17
|
+
if (!defaults) {
|
|
18
|
+
return res.json(null);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
res.json(defaults);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// POST /api/projects/:id/session-defaults - Update/create session defaults for project
|
|
25
|
+
router.post('/', (req, res) => {
|
|
26
|
+
const project = projects.getById(req.params.id);
|
|
27
|
+
if (!project) {
|
|
28
|
+
return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const result = ProjectSessionDefaultsRequest.safeParse(req.body);
|
|
32
|
+
if (!result.success) {
|
|
33
|
+
return res.status(400).json({ error: result.error.issues[0].message });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const updated = projectDefaults.upsert(req.params.id, result.data);
|
|
37
|
+
res.status(200).json(updated);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// DELETE /api/projects/:id/session-defaults - Reset session defaults for project
|
|
41
|
+
router.delete('/', (req, res) => {
|
|
42
|
+
const project = projects.getById(req.params.id);
|
|
43
|
+
if (!project) {
|
|
44
|
+
return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
projectDefaults.resetToDefaults(req.params.id);
|
|
48
|
+
res.json({ message: 'Session defaults reset to system defaults' });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export default router;
|