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
|
@@ -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;
|
|
@@ -1,26 +1,18 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
|
-
import { projects, sessions,
|
|
2
|
+
import { projects, sessions, commandRuns } from '../database.js';
|
|
3
3
|
import { commandRunner } from '../services/commandRunner.js';
|
|
4
|
-
import { CreateProjectRequest, UpdateProjectRequest
|
|
5
|
-
import { ProjectDefaultsRepository } from '../db/ProjectDefaultsRepository.js';
|
|
6
|
-
import { CreateSessionTemplateRequest } from '../../../shared/src/contracts/templates.js';
|
|
7
|
-
import { broadcastToProject } from '../websocket.js';
|
|
4
|
+
import { CreateProjectRequest, UpdateProjectRequest } from '../../../shared/src/contracts/projects.js';
|
|
8
5
|
import projectCommandButtonsRouter from './projects-commandButtons.js';
|
|
9
|
-
import
|
|
6
|
+
import projectSessionDefaultsRouter from './projects-session-defaults.js';
|
|
7
|
+
import projectTemplatesRouter from './projects-templates.js';
|
|
10
8
|
import { handleUploadError, uploadMiddleware } from '../middleware/upload.js';
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
prepareSessionConfig,
|
|
14
|
-
applyTemplateOverrides,
|
|
15
|
-
resolveNextTemplateId,
|
|
16
|
-
determineInitialStatus,
|
|
17
|
-
buildSchedulingUpdate,
|
|
18
|
-
setupAndStartSession,
|
|
19
|
-
} from './projects-session-helpers.js';
|
|
20
|
-
import { validateGitSettings, buildRunsBySession } from './projects-helpers.js';
|
|
9
|
+
import { determineInitialStatus } from './projects-session-helpers.js';
|
|
10
|
+
import { buildRunsBySession } from './projects-helpers.js';
|
|
21
11
|
import { resolveAgentTypeFromModel } from '../services/sessionProvider.js';
|
|
22
12
|
import { access, constants } from 'fs/promises';
|
|
23
13
|
import { dirname, isAbsolute } from 'path';
|
|
14
|
+
import { getRepositoryUrl } from '../services/gitService.js';
|
|
15
|
+
import { validateAndPrepareSessionConfig, createSessionRow, startSessionOrFail } from './projects-session-create.js';
|
|
24
16
|
|
|
25
17
|
// Error message constants
|
|
26
18
|
const ERR_PROJECT_NOT_FOUND = 'Project not found';
|
|
@@ -65,17 +57,31 @@ router.post('/', async (req, res) => {
|
|
|
65
57
|
return res.status(400).json({ error: result.error.issues[0].message });
|
|
66
58
|
}
|
|
67
59
|
|
|
68
|
-
const { name, workingDirectory, systemPrompt, onSessionCreated, onSessionDeleted, worktreePath, kanbanEnabled } = result.data;
|
|
60
|
+
const { name, workingDirectory, systemPrompt, onSessionCreated, onSessionDeleted, worktreePath, kanbanEnabled, repoUrl } = result.data;
|
|
69
61
|
|
|
70
62
|
const pathError = await validateWorktreePath(worktreePath);
|
|
71
63
|
if (pathError) {
|
|
72
64
|
return res.status(400).json({ error: pathError });
|
|
73
65
|
}
|
|
74
66
|
|
|
67
|
+
// Resolve repoUrl:
|
|
68
|
+
// - string: use as-is (explicitly provided)
|
|
69
|
+
// - null: suppress detection (explicitly sent as null)
|
|
70
|
+
// - undefined: auto-detect from git remote
|
|
71
|
+
let resolvedRepoUrl = repoUrl;
|
|
72
|
+
if (resolvedRepoUrl === undefined) {
|
|
73
|
+
try {
|
|
74
|
+
resolvedRepoUrl = await getRepositoryUrl(workingDirectory);
|
|
75
|
+
} catch {
|
|
76
|
+
resolvedRepoUrl = null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
75
80
|
const createOptions = {
|
|
76
81
|
onSessionCreated: onSessionCreated || null,
|
|
77
82
|
onSessionDeleted: onSessionDeleted || null,
|
|
78
83
|
worktreePath: worktreePath || null,
|
|
84
|
+
repoUrl: resolvedRepoUrl,
|
|
79
85
|
};
|
|
80
86
|
if (kanbanEnabled !== undefined) {
|
|
81
87
|
createOptions.kanbanEnabled = kanbanEnabled;
|
|
@@ -198,98 +204,6 @@ router.get('/:id/sessions', (req, res) => {
|
|
|
198
204
|
}
|
|
199
205
|
});
|
|
200
206
|
|
|
201
|
-
/**
|
|
202
|
-
* Validate and prepare the session configuration from the request body.
|
|
203
|
-
* Returns { config, nextTemplateId } on success, or { error, status } on failure.
|
|
204
|
-
*/
|
|
205
|
-
async function validateAndPrepareSessionConfig(reqBody, reqFiles, projectId, project) {
|
|
206
|
-
const projectDefs = projectDefaults.getByProjectId(projectId);
|
|
207
|
-
const systemDefaults = ProjectDefaultsRepository.getSystemDefaults();
|
|
208
|
-
const config = prepareSessionConfig(reqBody, projectDefs, systemDefaults);
|
|
209
|
-
config.files = reqFiles || [];
|
|
210
|
-
|
|
211
|
-
if (!config.prompt) {
|
|
212
|
-
return { error: 'Prompt is required', status: 400 };
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (config.parentSessionId) {
|
|
216
|
-
const parentSession = sessions.getById(config.parentSessionId);
|
|
217
|
-
if (!parentSession) {
|
|
218
|
-
return { error: 'Parent session not found', status: 404 };
|
|
219
|
-
}
|
|
220
|
-
if (parentSession.projectId !== projectId) {
|
|
221
|
-
return { error: 'Parent session does not belong to this project', status: 400 };
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Apply template overrides and resolve nextTemplateId
|
|
226
|
-
applyTemplateOverrides(config);
|
|
227
|
-
const { nextTemplateId, error: nextTemplateError } = resolveNextTemplateId(reqBody, config.nextTemplateId || null);
|
|
228
|
-
if (nextTemplateError) {
|
|
229
|
-
return { error: nextTemplateError, status: 400 };
|
|
230
|
-
}
|
|
231
|
-
config.nextTemplateId = nextTemplateId;
|
|
232
|
-
|
|
233
|
-
// Validate git settings for git repos
|
|
234
|
-
const { config: updatedConfig, error: gitError } = await validateGitSettings(config, project);
|
|
235
|
-
if (gitError) {
|
|
236
|
-
return { error: gitError, status: 400 };
|
|
237
|
-
}
|
|
238
|
-
Object.assign(config, updatedConfig);
|
|
239
|
-
|
|
240
|
-
return { config, nextTemplateId };
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Create the session row and apply any post-create updates.
|
|
245
|
-
* Returns the created session (already persisted in DB).
|
|
246
|
-
*/
|
|
247
|
-
function createSessionRow(projectId, config, nextTemplateId, initialStatus) {
|
|
248
|
-
const sessionName = config.name || generateInitialName(config.prompt);
|
|
249
|
-
const session = sessions.create(projectId, sessionName, config.prompt, {
|
|
250
|
-
mode: config.mode,
|
|
251
|
-
thinkingEnabled: config.thinkingEnabled,
|
|
252
|
-
gitBranch: config.gitBranch,
|
|
253
|
-
parentSessionId: config.parentSessionId,
|
|
254
|
-
status: initialStatus,
|
|
255
|
-
model: config.model,
|
|
256
|
-
providerId: config.providerId,
|
|
257
|
-
effortLevel: config.effortLevel,
|
|
258
|
-
agentType: config.agentType,
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
const postCreateUpdate = {
|
|
262
|
-
...(nextTemplateId ? { nextTemplateId } : {}),
|
|
263
|
-
...buildSchedulingUpdate(config, initialStatus),
|
|
264
|
-
};
|
|
265
|
-
if (Object.keys(postCreateUpdate).length > 0) {
|
|
266
|
-
sessions.update(session.id, postCreateUpdate);
|
|
267
|
-
}
|
|
268
|
-
return session;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Run setupAndStartSession and translate any failure into an error response,
|
|
273
|
-
* marking the session as errored and broadcasting the update.
|
|
274
|
-
*/
|
|
275
|
-
async function startSessionOrFail(req, res, { session, config, project }) {
|
|
276
|
-
try {
|
|
277
|
-
const { updatedSession } = await setupAndStartSession({
|
|
278
|
-
session, config, project, projectId: req.params.id, files: config.files,
|
|
279
|
-
});
|
|
280
|
-
return res.status(201).json(updatedSession);
|
|
281
|
-
} catch (error) {
|
|
282
|
-
console.error('Git setup error:', error);
|
|
283
|
-
const updatedSession = sessions.update(session.id, { status: 'error', error: error.message });
|
|
284
|
-
broadcastToProject(req.params.id, WS_MESSAGE_TYPES.SESSION_UPDATED, {
|
|
285
|
-
projectId: req.params.id,
|
|
286
|
-
sessionId: session.id,
|
|
287
|
-
session: updatedSession,
|
|
288
|
-
});
|
|
289
|
-
return res.status(500).json({ error: `Git setup failed: ${error.message}` });
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
207
|
// POST /api/projects/:id/sessions - Create session
|
|
294
208
|
// Supports both JSON and multipart/form-data (for file attachments)
|
|
295
209
|
router.post('/:id/sessions', uploadMiddleware('files', 10), handleUploadError, async (req, res) => {
|
|
@@ -335,79 +249,13 @@ router.post('/:id/sessions', uploadMiddleware('files', 10), handleUploadError, a
|
|
|
335
249
|
}
|
|
336
250
|
});
|
|
337
251
|
|
|
338
|
-
//
|
|
339
|
-
router.
|
|
340
|
-
const project = projects.getById(req.params.id);
|
|
341
|
-
if (!project) {
|
|
342
|
-
return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
const available = sessionTemplates.getAvailableForProject(req.params.id);
|
|
346
|
-
res.json(available);
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
// POST /api/projects/:id/templates - Create project template
|
|
350
|
-
router.post('/:id/templates', (req, res) => {
|
|
351
|
-
const project = projects.getById(req.params.id);
|
|
352
|
-
if (!project) {
|
|
353
|
-
return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const result = CreateSessionTemplateRequest.safeParse(req.body);
|
|
357
|
-
if (!result.success) {
|
|
358
|
-
return res.status(400).json({ error: result.error.issues[0].message });
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
const template = sessionTemplates.create({
|
|
362
|
-
projectId: req.params.id,
|
|
363
|
-
...result.data,
|
|
364
|
-
});
|
|
365
|
-
res.status(201).json(template);
|
|
366
|
-
});
|
|
252
|
+
// Template routes are mounted as a sub-router
|
|
253
|
+
router.use('/:id/templates', projectTemplatesRouter);
|
|
367
254
|
|
|
368
255
|
// Command button routes are mounted as a sub-router
|
|
369
|
-
router.use('/:id/
|
|
370
|
-
|
|
371
|
-
// GET /api/projects/:id/session-defaults - Get session defaults for project
|
|
372
|
-
router.get('/:id/session-defaults', (req, res) => {
|
|
373
|
-
const project = projects.getById(req.params.id);
|
|
374
|
-
if (!project) {
|
|
375
|
-
return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
const defaults = projectDefaults.getByProjectId(req.params.id);
|
|
379
|
-
if (!defaults) {
|
|
380
|
-
return res.json(null);
|
|
381
|
-
}
|
|
256
|
+
router.use('/:id/circus-commands', projectCommandButtonsRouter);
|
|
382
257
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
// POST /api/projects/:id/session-defaults - Update/create session defaults for project
|
|
387
|
-
router.post('/:id/session-defaults', (req, res) => {
|
|
388
|
-
const project = projects.getById(req.params.id);
|
|
389
|
-
if (!project) {
|
|
390
|
-
return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
const result = ProjectSessionDefaultsRequest.safeParse(req.body);
|
|
394
|
-
if (!result.success) {
|
|
395
|
-
return res.status(400).json({ error: result.error.issues[0].message });
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
const updated = projectDefaults.upsert(req.params.id, result.data);
|
|
399
|
-
res.status(200).json(updated);
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
// DELETE /api/projects/:id/session-defaults - Reset session defaults for project
|
|
403
|
-
router.delete('/:id/session-defaults', (req, res) => {
|
|
404
|
-
const project = projects.getById(req.params.id);
|
|
405
|
-
if (!project) {
|
|
406
|
-
return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
projectDefaults.resetToDefaults(req.params.id);
|
|
410
|
-
res.json({ message: 'Session defaults reset to system defaults' });
|
|
411
|
-
});
|
|
258
|
+
// Session defaults routes are mounted as a sub-router
|
|
259
|
+
router.use('/:id/session-defaults', projectSessionDefaultsRouter);
|
|
412
260
|
|
|
413
261
|
export default router;
|
|
@@ -6,6 +6,9 @@ import { requireRootSessionAndProject } from '../middleware/sessionLookup.js';
|
|
|
6
6
|
import { commandRunner } from '../services/commandRunner.js';
|
|
7
7
|
import { databaseManager } from '../db/DatabaseManager.js';
|
|
8
8
|
|
|
9
|
+
// Error message constants
|
|
10
|
+
const ERR_BUTTON_NOT_FOUND = 'Circus Command not found';
|
|
11
|
+
|
|
9
12
|
const router = Router();
|
|
10
13
|
|
|
11
14
|
/**
|
|
@@ -46,13 +49,13 @@ function broadcastCommandError(ctx, errorMessage) {
|
|
|
46
49
|
broadcastToProject(projectId, WS_MESSAGE_TYPES.COMMAND_RUN_ERROR, { projectId, sessionId, runId, buttonId, error: errorMessage });
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
// GET /api/sessions/:id/
|
|
50
|
-
router.get('/:id/
|
|
52
|
+
// GET /api/sessions/:id/circus-commands - List command buttons for the workflow project
|
|
53
|
+
router.get('/:id/circus-commands', requireRootSessionAndProject, (req, res) => {
|
|
51
54
|
res.json(commandButtons.getByProjectId(req.rootSession_.projectId));
|
|
52
55
|
});
|
|
53
56
|
|
|
54
|
-
// POST /api/sessions/:id/
|
|
55
|
-
router.post('/:id/
|
|
57
|
+
// POST /api/sessions/:id/circus-commands/:buttonId/run - Execute button command
|
|
58
|
+
router.post('/:id/circus-commands/:buttonId/run', requireRootSessionAndProject, (req, res) => {
|
|
56
59
|
const sessionId = req.rootSessionId;
|
|
57
60
|
const buttonId = req.params.buttonId;
|
|
58
61
|
|
|
@@ -60,10 +63,10 @@ router.post('/:id/command-buttons/:buttonId/run', requireRootSessionAndProject,
|
|
|
60
63
|
|
|
61
64
|
const button = commandButtons.getById(buttonId);
|
|
62
65
|
if (!button) {
|
|
63
|
-
return res.status(404).json({ error:
|
|
66
|
+
return res.status(404).json({ error: ERR_BUTTON_NOT_FOUND });
|
|
64
67
|
}
|
|
65
68
|
if (button.projectId !== req.rootSession_.projectId) {
|
|
66
|
-
return res.status(404).json({ error:
|
|
69
|
+
return res.status(404).json({ error: ERR_BUTTON_NOT_FOUND });
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
// Generate run ID
|
|
@@ -105,16 +108,16 @@ router.post('/:id/command-buttons/:buttonId/run', requireRootSessionAndProject,
|
|
|
105
108
|
})();
|
|
106
109
|
});
|
|
107
110
|
|
|
108
|
-
// GET /api/sessions/:id/
|
|
109
|
-
router.get('/:id/
|
|
111
|
+
// GET /api/sessions/:id/circus-commands/runs - Get active runs for session
|
|
112
|
+
router.get('/:id/circus-commands/runs', requireRootSessionAndProject, (req, res) => {
|
|
110
113
|
const sessionId = req.rootSessionId;
|
|
111
114
|
|
|
112
115
|
const activeRuns = commandRunner.getRunsBySession(sessionId);
|
|
113
116
|
res.json(activeRuns);
|
|
114
117
|
});
|
|
115
118
|
|
|
116
|
-
// GET /api/sessions/:id/
|
|
117
|
-
router.get('/:id/
|
|
119
|
+
// GET /api/sessions/:id/circus-commands/runs/:runId - Get single run by ID
|
|
120
|
+
router.get('/:id/circus-commands/runs/:runId', requireRootSessionAndProject, (req, res) => {
|
|
118
121
|
const { runId } = req.params;
|
|
119
122
|
const sessionId = req.rootSessionId;
|
|
120
123
|
|
|
@@ -144,8 +147,8 @@ router.get('/:id/command-buttons/runs/:runId', requireRootSessionAndProject, (re
|
|
|
144
147
|
});
|
|
145
148
|
});
|
|
146
149
|
|
|
147
|
-
// DELETE /api/sessions/:id/
|
|
148
|
-
router.delete('/:id/
|
|
150
|
+
// DELETE /api/sessions/:id/circus-commands/runs/:runId - Delete a command run record
|
|
151
|
+
router.delete('/:id/circus-commands/runs/:runId', requireRootSessionAndProject, (req, res) => {
|
|
149
152
|
const sessionId = req.rootSessionId;
|
|
150
153
|
const { runId } = req.params;
|
|
151
154
|
|
|
@@ -178,17 +181,17 @@ router.delete('/:id/command-buttons/runs/:runId', requireRootSessionAndProject,
|
|
|
178
181
|
res.status(204).send();
|
|
179
182
|
});
|
|
180
183
|
|
|
181
|
-
// DELETE /api/sessions/:id/
|
|
182
|
-
router.delete('/:id/
|
|
184
|
+
// DELETE /api/sessions/:id/circus-commands/:buttonId/runs/all - Delete all runs for a button in a session
|
|
185
|
+
router.delete('/:id/circus-commands/:buttonId/runs/all', requireRootSessionAndProject, (req, res) => {
|
|
183
186
|
const sessionId = req.rootSessionId;
|
|
184
187
|
const { buttonId } = req.params;
|
|
185
188
|
|
|
186
189
|
const button = commandButtons.getById(buttonId);
|
|
187
190
|
if (!button) {
|
|
188
|
-
return res.status(404).json({ error:
|
|
191
|
+
return res.status(404).json({ error: ERR_BUTTON_NOT_FOUND });
|
|
189
192
|
}
|
|
190
193
|
if (button.projectId !== req.rootSession_.projectId) {
|
|
191
|
-
return res.status(404).json({ error:
|
|
194
|
+
return res.status(404).json({ error: ERR_BUTTON_NOT_FOUND });
|
|
192
195
|
}
|
|
193
196
|
|
|
194
197
|
const { deletedRuns } = commandRuns.deleteByButtonAndSession(buttonId, sessionId);
|
|
@@ -213,8 +216,8 @@ router.delete('/:id/command-buttons/:buttonId/runs/all', requireRootSessionAndPr
|
|
|
213
216
|
res.status(204).send();
|
|
214
217
|
});
|
|
215
218
|
|
|
216
|
-
// POST /api/sessions/:id/
|
|
217
|
-
router.post('/:id/
|
|
219
|
+
// POST /api/sessions/:id/circus-commands/runs/:runId/kill - Kill running command
|
|
220
|
+
router.post('/:id/circus-commands/runs/:runId/kill', requireRootSessionAndProject, (req, res) => {
|
|
218
221
|
const sessionId = req.rootSessionId;
|
|
219
222
|
const runId = req.params.runId;
|
|
220
223
|
|