circuschief 0.8.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/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/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 +225 -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 +165 -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/session-helpers.js +26 -1
- package/packages/server/src/schema.sql +4 -0
- package/packages/server/src/services/commandButtonPrompts.js +9 -7
- package/packages/server/src/services/gitCommitAttribution.js +38 -8
- 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 +37 -309
- package/packages/server/src/services/gitWorktree.js +127 -0
- package/packages/server/src/services/sessionPrompts.js +1 -1
- package/packages/shared/src/contracts/sessions.js +27 -1
- package/packages/shared/src/contracts/templates.js +10 -0
- package/packages/web/dist/assets/{ActiveSessionsView-B0XHqLmv.js → ActiveSessionsView-Cxh8mHmB.js} +1 -1
- package/packages/web/dist/assets/{AgentLogsView-DmsjUMlB.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/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-D1nI8_zk.js → GeneralSettingsView-sPXkLlLy.js} +1 -1
- package/packages/web/dist/assets/{InputWithButton-CAkttyqx.js → InputWithButton-B-o0DgMH.js} +1 -1
- package/packages/web/dist/assets/{InterpolationHelp-BO1j9Z3_.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-BSxKUSus.css → ModelSelector-BNYKujL-.css} +1 -1
- package/packages/web/dist/assets/NewSessionView-BR_COfgW.js +3 -0
- package/packages/web/dist/assets/{NewSessionView-BDPb-1qr.css → NewSessionView-DBl7T2Xp.css} +1 -1
- 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-DcNyuINs.js → ProjectListView-CYmmAcBD.js} +1 -1
- package/packages/web/dist/assets/{ProjectNewView-B5YV62hv.js → ProjectNewView-DEhqw3Jv.js} +1 -1
- package/packages/web/dist/assets/ProvidersView-XZh3jkmH.js +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-B6AxyREh.js → SessionFormOptions-hqijxc0S.js} +1 -1
- package/packages/web/dist/assets/{SessionListView-B5_6gW49.css → SessionListView-3-xx6EVs.css} +1 -1
- package/packages/web/dist/assets/SessionListView-DYXHM9I-.js +1 -0
- package/packages/web/dist/assets/{SessionLogStream-LlZ3z_Xj.js → SessionLogStream-5NfVr9pF.js} +6 -6
- package/packages/web/dist/assets/{SettingsView-CTGiGvR2.js → SettingsView-DI8ncOAV.js} +1 -1
- package/packages/web/dist/assets/{SlashCommandWizard-Cy04d7-o.js → SlashCommandWizard-BQ_rMzn-.js} +1 -1
- package/packages/web/dist/assets/{SummarySettingsView-BR2ZjEa3.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-BfqR-fqq.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-DgkC10TW.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-DtfUt785.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-BY174HVJ.css → index-uySCcnA_.css} +1 -1
- package/packages/web/dist/assets/projectDefaults-B8esIcYq.js +1 -0
- package/packages/web/dist/assets/{projects-DXYQNJIi.js → projects-C-8PSxKi.js} +1 -1
- package/packages/web/dist/assets/{providers-1bnH-exJ.js → providers-oXifvvqN.js} +1 -1
- package/packages/web/dist/assets/{sessions-6zGUlFrt.js → sessions-Nq5VafSf.js} +1 -1
- package/packages/web/dist/assets/{settings-MbfRir0d.js → settings-DtpuiyT6.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-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/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-fK8FIZgP.js +0 -83
- 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
package/package.json
CHANGED
|
@@ -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
|
|
|
@@ -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) {
|
|
@@ -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;
|
|
@@ -6,6 +6,9 @@ import { executeHookAsync } from '../services/hookService.js';
|
|
|
6
6
|
import { broadcastToProject } from '../websocket.js';
|
|
7
7
|
import { WS_MESSAGE_TYPES, DEFAULT_RESCHEDULE_DELAY_MINUTES } from '../../../shared/src/index.js';
|
|
8
8
|
|
|
9
|
+
const SCHEDULED_AT_FORMAT_MESSAGE = 'scheduledAt must be a valid ISO 8601 date-time string with a timezone, for example "2026-06-12T14:00:00Z".';
|
|
10
|
+
const ISO_8601_DATE_TIME_WITH_TIMEZONE = /^(\d{4})-(\d{2})-(\d{2})T([01]\d|2[0-3]):([0-5]\d):([0-5]\d)(?:\.\d+)?(Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/;
|
|
11
|
+
|
|
9
12
|
/**
|
|
10
13
|
* Generate an initial session name from the prompt
|
|
11
14
|
* This will be replaced by a better name when the summary is generated
|
|
@@ -83,14 +86,57 @@ export function resolveThinkingEnabled(body, projectDefs, systemDefaults) {
|
|
|
83
86
|
return systemDefaults.thinkingEnabled;
|
|
84
87
|
}
|
|
85
88
|
|
|
89
|
+
function hasValidDateParts(year, month, day) {
|
|
90
|
+
const parsed = new Date(Date.UTC(year, month - 1, day));
|
|
91
|
+
return parsed.getUTCFullYear() === year
|
|
92
|
+
&& parsed.getUTCMonth() === month - 1
|
|
93
|
+
&& parsed.getUTCDate() === day;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Parse scheduledAt from the public API contract.
|
|
98
|
+
* @param {*} value - Raw scheduledAt value
|
|
99
|
+
* @returns {{ value: number|undefined, error: string|null }}
|
|
100
|
+
*/
|
|
101
|
+
export function parseScheduledAt(value) {
|
|
102
|
+
if (value === undefined || value === null || value === '') {
|
|
103
|
+
return { value: undefined, error: null };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (typeof value !== 'string') {
|
|
107
|
+
return { value: undefined, error: SCHEDULED_AT_FORMAT_MESSAGE };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const match = ISO_8601_DATE_TIME_WITH_TIMEZONE.exec(value);
|
|
111
|
+
if (!match) {
|
|
112
|
+
return { value: undefined, error: SCHEDULED_AT_FORMAT_MESSAGE };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const year = Number(match[1]);
|
|
116
|
+
const month = Number(match[2]);
|
|
117
|
+
const day = Number(match[3]);
|
|
118
|
+
if (!hasValidDateParts(year, month, day)) {
|
|
119
|
+
return { value: undefined, error: SCHEDULED_AT_FORMAT_MESSAGE };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const timestamp = Date.parse(value);
|
|
123
|
+
if (!Number.isFinite(timestamp)) {
|
|
124
|
+
return { value: undefined, error: SCHEDULED_AT_FORMAT_MESSAGE };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { value: timestamp, error: null };
|
|
128
|
+
}
|
|
129
|
+
|
|
86
130
|
/**
|
|
87
131
|
* Parse scheduling fields from request body.
|
|
88
132
|
* @param {object} body - Request body
|
|
89
133
|
* @returns {object} Scheduling configuration
|
|
90
134
|
*/
|
|
91
135
|
export function parseSchedulingConfig(body) {
|
|
136
|
+
const scheduledAt = parseScheduledAt(body.scheduledAt);
|
|
92
137
|
return {
|
|
93
|
-
scheduledAt:
|
|
138
|
+
scheduledAt: scheduledAt.value,
|
|
139
|
+
schedulingError: scheduledAt.error,
|
|
94
140
|
autoRescheduleEnabled: body.autoRescheduleEnabled === true || body.autoRescheduleEnabled === 'true',
|
|
95
141
|
rescheduleDelayMinutes: body.rescheduleDelayMinutes ? parseInt(body.rescheduleDelayMinutes, 10) : DEFAULT_RESCHEDULE_DELAY_MINUTES,
|
|
96
142
|
rescheduleOnTokenLimit: body.rescheduleOnTokenLimit !== false && body.rescheduleOnTokenLimit !== 'false',
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { projects, sessionTemplates } from '../database.js';
|
|
3
|
+
import { CreateSessionTemplateRequest } from '../../../shared/src/contracts/templates.js';
|
|
4
|
+
|
|
5
|
+
const ERR_PROJECT_NOT_FOUND = 'Project not found';
|
|
6
|
+
const router = Router({ mergeParams: true });
|
|
7
|
+
|
|
8
|
+
// GET - List available templates for project (project + global)
|
|
9
|
+
router.get('/', (req, res) => {
|
|
10
|
+
const project = projects.getById(req.params.id);
|
|
11
|
+
if (!project) {
|
|
12
|
+
return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const available = sessionTemplates.getAvailableForProject(req.params.id);
|
|
16
|
+
res.json(available);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// POST - Create project template
|
|
20
|
+
router.post('/', (req, res) => {
|
|
21
|
+
const project = projects.getById(req.params.id);
|
|
22
|
+
if (!project) {
|
|
23
|
+
return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const result = CreateSessionTemplateRequest.safeParse(req.body);
|
|
27
|
+
if (!result.success) {
|
|
28
|
+
return res.status(400).json({ error: result.error.issues[0].message });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const template = sessionTemplates.create({
|
|
32
|
+
projectId: req.params.id,
|
|
33
|
+
...result.data,
|
|
34
|
+
});
|
|
35
|
+
res.status(201).json(template);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export default router;
|