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
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { sessions, sessionTemplates, attachments } from '../database.js';
|
|
2
2
|
import * as slashCommandService from '../services/slashCommandService.js';
|
|
3
3
|
import { setupGitForSession } from '../services/gitSessionSetup.js';
|
|
4
|
+
import { resolveProviderMetadataFromModel } from '../services/sessionProvider.js';
|
|
4
5
|
import { executeHookAsync } from '../services/hookService.js';
|
|
5
6
|
import { broadcastToProject } from '../websocket.js';
|
|
6
|
-
import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
|
|
7
|
+
import { WS_MESSAGE_TYPES, DEFAULT_RESCHEDULE_DELAY_MINUTES } from '../../../shared/src/index.js';
|
|
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)$/;
|
|
7
11
|
|
|
8
12
|
/**
|
|
9
13
|
* Generate an initial session name from the prompt
|
|
@@ -82,16 +86,59 @@ export function resolveThinkingEnabled(body, projectDefs, systemDefaults) {
|
|
|
82
86
|
return systemDefaults.thinkingEnabled;
|
|
83
87
|
}
|
|
84
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
|
+
|
|
85
130
|
/**
|
|
86
131
|
* Parse scheduling fields from request body.
|
|
87
132
|
* @param {object} body - Request body
|
|
88
133
|
* @returns {object} Scheduling configuration
|
|
89
134
|
*/
|
|
90
135
|
export function parseSchedulingConfig(body) {
|
|
136
|
+
const scheduledAt = parseScheduledAt(body.scheduledAt);
|
|
91
137
|
return {
|
|
92
|
-
scheduledAt:
|
|
138
|
+
scheduledAt: scheduledAt.value,
|
|
139
|
+
schedulingError: scheduledAt.error,
|
|
93
140
|
autoRescheduleEnabled: body.autoRescheduleEnabled === true || body.autoRescheduleEnabled === 'true',
|
|
94
|
-
rescheduleDelayMinutes: body.rescheduleDelayMinutes ? parseInt(body.rescheduleDelayMinutes, 10) :
|
|
141
|
+
rescheduleDelayMinutes: body.rescheduleDelayMinutes ? parseInt(body.rescheduleDelayMinutes, 10) : DEFAULT_RESCHEDULE_DELAY_MINUTES,
|
|
95
142
|
rescheduleOnTokenLimit: body.rescheduleOnTokenLimit !== false && body.rescheduleOnTokenLimit !== 'false',
|
|
96
143
|
rescheduleOnServiceError: body.rescheduleOnServiceError !== false && body.rescheduleOnServiceError !== 'false',
|
|
97
144
|
maxRescheduleCount: body.maxRescheduleCount ? parseInt(body.maxRescheduleCount, 10) : null,
|
|
@@ -146,7 +193,7 @@ export function prepareSessionConfig(body, projectDefs, systemDefaults) {
|
|
|
146
193
|
providerId: resolveProviderDefault(explicitProviderId, projectProviderId, systemProviderId),
|
|
147
194
|
effortLevel,
|
|
148
195
|
gitBranch: resolveDefault(body.gitBranch, projectDefs?.gitBranch, null),
|
|
149
|
-
gitMode: resolveDefault(body.gitMode, projectDefs?.gitMode,
|
|
196
|
+
gitMode: resolveDefault(body.gitMode, projectDefs?.gitMode, systemDefaults.gitMode),
|
|
150
197
|
templateId: body.templateId,
|
|
151
198
|
parentSessionId: body.parentSessionId || null,
|
|
152
199
|
files: [],
|
|
@@ -260,12 +307,17 @@ async function resolveSessionWorkingDirectory({ session, config, project }) {
|
|
|
260
307
|
};
|
|
261
308
|
}
|
|
262
309
|
|
|
310
|
+
// Normalize 'current' mode to null (no git isolation) for setupGitForSession
|
|
311
|
+
const normalizedGitMode = (config.gitMode === 'current') ? null : (config.gitMode || null);
|
|
312
|
+
|
|
263
313
|
const gitSetup = await setupGitForSession({
|
|
264
314
|
projectDir: project.workingDirectory,
|
|
265
|
-
gitMode:
|
|
315
|
+
gitMode: normalizedGitMode,
|
|
266
316
|
gitBranch: config.gitBranch || null,
|
|
267
317
|
sessionId: session.id,
|
|
268
318
|
worktreeBasePath: project.worktreePath || null,
|
|
319
|
+
commitAttributionOverride:
|
|
320
|
+
resolveProviderMetadataFromModel(config.model)?.commitAttributionOverride ?? null,
|
|
269
321
|
});
|
|
270
322
|
return { workingDirectory: gitSetup.workingDirectory, gitWorktree: gitSetup.gitWorktree };
|
|
271
323
|
}
|
|
@@ -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,88 +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
|
-
// Apply template overrides and resolve nextTemplateId
|
|
216
|
-
applyTemplateOverrides(config);
|
|
217
|
-
const { nextTemplateId, error: nextTemplateError } = resolveNextTemplateId(reqBody, config.nextTemplateId || null);
|
|
218
|
-
if (nextTemplateError) {
|
|
219
|
-
return { error: nextTemplateError, status: 400 };
|
|
220
|
-
}
|
|
221
|
-
config.nextTemplateId = nextTemplateId;
|
|
222
|
-
|
|
223
|
-
// Validate git settings for git repos
|
|
224
|
-
const { config: updatedConfig, error: gitError } = await validateGitSettings(config, project);
|
|
225
|
-
if (gitError) {
|
|
226
|
-
return { error: gitError, status: 400 };
|
|
227
|
-
}
|
|
228
|
-
Object.assign(config, updatedConfig);
|
|
229
|
-
|
|
230
|
-
return { config, nextTemplateId };
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Create the session row and apply any post-create updates.
|
|
235
|
-
* Returns the created session (already persisted in DB).
|
|
236
|
-
*/
|
|
237
|
-
function createSessionRow(projectId, config, nextTemplateId, initialStatus) {
|
|
238
|
-
const sessionName = config.name || generateInitialName(config.prompt);
|
|
239
|
-
const session = sessions.create(projectId, sessionName, config.prompt, {
|
|
240
|
-
mode: config.mode,
|
|
241
|
-
thinkingEnabled: config.thinkingEnabled,
|
|
242
|
-
gitBranch: config.gitBranch,
|
|
243
|
-
parentSessionId: config.parentSessionId,
|
|
244
|
-
status: initialStatus,
|
|
245
|
-
model: config.model,
|
|
246
|
-
providerId: config.providerId,
|
|
247
|
-
effortLevel: config.effortLevel,
|
|
248
|
-
agentType: config.agentType,
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
const postCreateUpdate = {
|
|
252
|
-
...(nextTemplateId ? { nextTemplateId } : {}),
|
|
253
|
-
...buildSchedulingUpdate(config, initialStatus),
|
|
254
|
-
};
|
|
255
|
-
if (Object.keys(postCreateUpdate).length > 0) {
|
|
256
|
-
sessions.update(session.id, postCreateUpdate);
|
|
257
|
-
}
|
|
258
|
-
return session;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Run setupAndStartSession and translate any failure into an error response,
|
|
263
|
-
* marking the session as errored and broadcasting the update.
|
|
264
|
-
*/
|
|
265
|
-
async function startSessionOrFail(req, res, { session, config, project }) {
|
|
266
|
-
try {
|
|
267
|
-
const { updatedSession } = await setupAndStartSession({
|
|
268
|
-
session, config, project, projectId: req.params.id, files: config.files,
|
|
269
|
-
});
|
|
270
|
-
return res.status(201).json(updatedSession);
|
|
271
|
-
} catch (error) {
|
|
272
|
-
console.error('Git setup error:', error);
|
|
273
|
-
const updatedSession = sessions.update(session.id, { status: 'error', error: error.message });
|
|
274
|
-
broadcastToProject(req.params.id, WS_MESSAGE_TYPES.SESSION_UPDATED, {
|
|
275
|
-
projectId: req.params.id,
|
|
276
|
-
sessionId: session.id,
|
|
277
|
-
session: updatedSession,
|
|
278
|
-
});
|
|
279
|
-
return res.status(500).json({ error: `Git setup failed: ${error.message}` });
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
207
|
// POST /api/projects/:id/sessions - Create session
|
|
284
208
|
// Supports both JSON and multipart/form-data (for file attachments)
|
|
285
209
|
router.post('/:id/sessions', uploadMiddleware('files', 10), handleUploadError, async (req, res) => {
|
|
@@ -325,79 +249,13 @@ router.post('/:id/sessions', uploadMiddleware('files', 10), handleUploadError, a
|
|
|
325
249
|
}
|
|
326
250
|
});
|
|
327
251
|
|
|
328
|
-
//
|
|
329
|
-
router.
|
|
330
|
-
const project = projects.getById(req.params.id);
|
|
331
|
-
if (!project) {
|
|
332
|
-
return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
const available = sessionTemplates.getAvailableForProject(req.params.id);
|
|
336
|
-
res.json(available);
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
// POST /api/projects/:id/templates - Create project template
|
|
340
|
-
router.post('/:id/templates', (req, res) => {
|
|
341
|
-
const project = projects.getById(req.params.id);
|
|
342
|
-
if (!project) {
|
|
343
|
-
return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const result = CreateSessionTemplateRequest.safeParse(req.body);
|
|
347
|
-
if (!result.success) {
|
|
348
|
-
return res.status(400).json({ error: result.error.issues[0].message });
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
const template = sessionTemplates.create({
|
|
352
|
-
projectId: req.params.id,
|
|
353
|
-
...result.data,
|
|
354
|
-
});
|
|
355
|
-
res.status(201).json(template);
|
|
356
|
-
});
|
|
252
|
+
// Template routes are mounted as a sub-router
|
|
253
|
+
router.use('/:id/templates', projectTemplatesRouter);
|
|
357
254
|
|
|
358
255
|
// Command button routes are mounted as a sub-router
|
|
359
|
-
router.use('/:id/
|
|
256
|
+
router.use('/:id/circus-commands', projectCommandButtonsRouter);
|
|
360
257
|
|
|
361
|
-
//
|
|
362
|
-
router.
|
|
363
|
-
const project = projects.getById(req.params.id);
|
|
364
|
-
if (!project) {
|
|
365
|
-
return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
const defaults = projectDefaults.getByProjectId(req.params.id);
|
|
369
|
-
if (!defaults) {
|
|
370
|
-
return res.json(null);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
res.json(defaults);
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
// POST /api/projects/:id/session-defaults - Update/create session defaults for project
|
|
377
|
-
router.post('/:id/session-defaults', (req, res) => {
|
|
378
|
-
const project = projects.getById(req.params.id);
|
|
379
|
-
if (!project) {
|
|
380
|
-
return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
const result = ProjectSessionDefaultsRequest.safeParse(req.body);
|
|
384
|
-
if (!result.success) {
|
|
385
|
-
return res.status(400).json({ error: result.error.issues[0].message });
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
const updated = projectDefaults.upsert(req.params.id, result.data);
|
|
389
|
-
res.status(200).json(updated);
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
// DELETE /api/projects/:id/session-defaults - Reset session defaults for project
|
|
393
|
-
router.delete('/:id/session-defaults', (req, res) => {
|
|
394
|
-
const project = projects.getById(req.params.id);
|
|
395
|
-
if (!project) {
|
|
396
|
-
return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
projectDefaults.resetToDefaults(req.params.id);
|
|
400
|
-
res.json({ message: 'Session defaults reset to system defaults' });
|
|
401
|
-
});
|
|
258
|
+
// Session defaults routes are mounted as a sub-router
|
|
259
|
+
router.use('/:id/session-defaults', projectSessionDefaultsRouter);
|
|
402
260
|
|
|
403
261
|
export default router;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import { modelProviders } from '../database.js';
|
|
3
3
|
import {
|
|
4
|
+
COMMIT_ATTRIBUTION_VALIDATION_MESSAGE,
|
|
4
5
|
CreateProviderRequest,
|
|
5
6
|
UpdateProviderRequest,
|
|
6
7
|
CreateProviderModelRequest,
|
|
@@ -34,6 +35,9 @@ router.get('/', (_req, res) => {
|
|
|
34
35
|
const sanitized = allProviders.map(redactAuthToken);
|
|
35
36
|
res.json(sanitized);
|
|
36
37
|
} catch (error) {
|
|
38
|
+
if (error.message === COMMIT_ATTRIBUTION_VALIDATION_MESSAGE) {
|
|
39
|
+
return res.status(400).json({ error: error.message });
|
|
40
|
+
}
|
|
37
41
|
res.status(500).json({ error: error.message });
|
|
38
42
|
}
|
|
39
43
|
});
|
|
@@ -82,9 +86,15 @@ router.patch('/:id', (req, res) => {
|
|
|
82
86
|
const updated = modelProviders.update(req.params.id, result.data);
|
|
83
87
|
res.json(redactAuthToken(updated));
|
|
84
88
|
} catch (error) {
|
|
85
|
-
if (
|
|
89
|
+
if (
|
|
90
|
+
error.message === 'Cannot delete built-in provider' ||
|
|
91
|
+
error.message.startsWith('Built-in providers can only update')
|
|
92
|
+
) {
|
|
86
93
|
return res.status(403).json({ error: error.message });
|
|
87
94
|
}
|
|
95
|
+
if (error.message === COMMIT_ATTRIBUTION_VALIDATION_MESSAGE) {
|
|
96
|
+
return res.status(400).json({ error: error.message });
|
|
97
|
+
}
|
|
88
98
|
res.status(500).json({ error: error.message });
|
|
89
99
|
}
|
|
90
100
|
});
|
|
@@ -2,10 +2,13 @@ import { Router } from 'express';
|
|
|
2
2
|
import { commandButtons, commandRuns } from '../database.js';
|
|
3
3
|
import { broadcastToSession, broadcastToProject } from '../websocket.js';
|
|
4
4
|
import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
|
|
5
|
-
import {
|
|
5
|
+
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,16 +49,24 @@ 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
|
-
//
|
|
50
|
-
router.
|
|
51
|
-
|
|
52
|
+
// GET /api/sessions/:id/circus-commands - List command buttons for the workflow project
|
|
53
|
+
router.get('/:id/circus-commands', requireRootSessionAndProject, (req, res) => {
|
|
54
|
+
res.json(commandButtons.getByProjectId(req.rootSession_.projectId));
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// POST /api/sessions/:id/circus-commands/:buttonId/run - Execute button command
|
|
58
|
+
router.post('/:id/circus-commands/:buttonId/run', requireRootSessionAndProject, (req, res) => {
|
|
59
|
+
const sessionId = req.rootSessionId;
|
|
52
60
|
const buttonId = req.params.buttonId;
|
|
53
61
|
|
|
54
62
|
console.log(`[RUN] Starting command for buttonId: ${buttonId}, sessionId: ${sessionId}`);
|
|
55
63
|
|
|
56
64
|
const button = commandButtons.getById(buttonId);
|
|
57
65
|
if (!button) {
|
|
58
|
-
return res.status(404).json({ error:
|
|
66
|
+
return res.status(404).json({ error: ERR_BUTTON_NOT_FOUND });
|
|
67
|
+
}
|
|
68
|
+
if (button.projectId !== req.rootSession_.projectId) {
|
|
69
|
+
return res.status(404).json({ error: ERR_BUTTON_NOT_FOUND });
|
|
59
70
|
}
|
|
60
71
|
|
|
61
72
|
// Generate run ID
|
|
@@ -67,8 +78,8 @@ router.post('/:id/command-buttons/:buttonId/run', requireSessionAndProject, (req
|
|
|
67
78
|
res.json({ runId, buttonId, status: 'running', output: '' });
|
|
68
79
|
|
|
69
80
|
// Capture middleware values for use in async callbacks
|
|
70
|
-
const projectId = req.
|
|
71
|
-
const workingDirectory = req.
|
|
81
|
+
const projectId = req.rootSession_.projectId;
|
|
82
|
+
const workingDirectory = req.rootWorkingDirectory;
|
|
72
83
|
const ctx = { sessionId, projectId, runId, buttonId };
|
|
73
84
|
|
|
74
85
|
// Broadcast initial "running" status immediately so session list can show the running indicator
|
|
@@ -97,17 +108,18 @@ router.post('/:id/command-buttons/:buttonId/run', requireSessionAndProject, (req
|
|
|
97
108
|
})();
|
|
98
109
|
});
|
|
99
110
|
|
|
100
|
-
// GET /api/sessions/:id/
|
|
101
|
-
router.get('/:id/
|
|
102
|
-
const sessionId = req.
|
|
111
|
+
// GET /api/sessions/:id/circus-commands/runs - Get active runs for session
|
|
112
|
+
router.get('/:id/circus-commands/runs', requireRootSessionAndProject, (req, res) => {
|
|
113
|
+
const sessionId = req.rootSessionId;
|
|
103
114
|
|
|
104
115
|
const activeRuns = commandRunner.getRunsBySession(sessionId);
|
|
105
116
|
res.json(activeRuns);
|
|
106
117
|
});
|
|
107
118
|
|
|
108
|
-
// GET /api/sessions/:id/
|
|
109
|
-
router.get('/:id/
|
|
110
|
-
const {
|
|
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) => {
|
|
121
|
+
const { runId } = req.params;
|
|
122
|
+
const sessionId = req.rootSessionId;
|
|
111
123
|
|
|
112
124
|
// Check if run is currently running (in memory)
|
|
113
125
|
if (commandRunner.isRunning(runId)) {
|
|
@@ -135,9 +147,9 @@ router.get('/:id/command-buttons/runs/:runId', requireSession, (req, res) => {
|
|
|
135
147
|
});
|
|
136
148
|
});
|
|
137
149
|
|
|
138
|
-
// DELETE /api/sessions/:id/
|
|
139
|
-
router.delete('/:id/
|
|
140
|
-
const sessionId = req.
|
|
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) => {
|
|
152
|
+
const sessionId = req.rootSessionId;
|
|
141
153
|
const { runId } = req.params;
|
|
142
154
|
|
|
143
155
|
const run = commandRuns.getById(runId);
|
|
@@ -151,7 +163,7 @@ router.delete('/:id/command-buttons/runs/:runId', requireSessionAndProject, (req
|
|
|
151
163
|
|
|
152
164
|
commandRuns.deleteById(runId);
|
|
153
165
|
|
|
154
|
-
const projectId = req.
|
|
166
|
+
const projectId = req.rootSession_.projectId;
|
|
155
167
|
|
|
156
168
|
// Broadcast deletion to session and project subscribers
|
|
157
169
|
broadcastToSession(sessionId, WS_MESSAGE_TYPES.COMMAND_RUN_DELETED, {
|
|
@@ -169,19 +181,22 @@ router.delete('/:id/command-buttons/runs/:runId', requireSessionAndProject, (req
|
|
|
169
181
|
res.status(204).send();
|
|
170
182
|
});
|
|
171
183
|
|
|
172
|
-
// DELETE /api/sessions/:id/
|
|
173
|
-
router.delete('/:id/
|
|
174
|
-
const sessionId = req.
|
|
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) => {
|
|
186
|
+
const sessionId = req.rootSessionId;
|
|
175
187
|
const { buttonId } = req.params;
|
|
176
188
|
|
|
177
189
|
const button = commandButtons.getById(buttonId);
|
|
178
190
|
if (!button) {
|
|
179
|
-
return res.status(404).json({ error:
|
|
191
|
+
return res.status(404).json({ error: ERR_BUTTON_NOT_FOUND });
|
|
192
|
+
}
|
|
193
|
+
if (button.projectId !== req.rootSession_.projectId) {
|
|
194
|
+
return res.status(404).json({ error: ERR_BUTTON_NOT_FOUND });
|
|
180
195
|
}
|
|
181
196
|
|
|
182
197
|
const { deletedRuns } = commandRuns.deleteByButtonAndSession(buttonId, sessionId);
|
|
183
198
|
|
|
184
|
-
const projectId = req.
|
|
199
|
+
const projectId = req.rootSession_.projectId;
|
|
185
200
|
|
|
186
201
|
// Broadcast individual COMMAND_RUN_DELETED events for each deleted run
|
|
187
202
|
for (const run of deletedRuns) {
|
|
@@ -201,13 +216,19 @@ router.delete('/:id/command-buttons/:buttonId/runs/all', requireSessionAndProjec
|
|
|
201
216
|
res.status(204).send();
|
|
202
217
|
});
|
|
203
218
|
|
|
204
|
-
// POST /api/sessions/:id/
|
|
205
|
-
router.post('/:id/
|
|
206
|
-
const sessionId = req.
|
|
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) => {
|
|
221
|
+
const sessionId = req.rootSessionId;
|
|
207
222
|
const runId = req.params.runId;
|
|
208
223
|
|
|
209
224
|
console.log(`[KILL] Kill request for runId: ${runId}, sessionId: ${sessionId}`);
|
|
210
225
|
|
|
226
|
+
const activeRuns = commandRunner.getRunsBySession(sessionId);
|
|
227
|
+
const activeRun = activeRuns.find((run) => run.runId === runId);
|
|
228
|
+
if (!activeRun) {
|
|
229
|
+
return res.status(404).json({ error: 'Run not found or already completed' });
|
|
230
|
+
}
|
|
231
|
+
|
|
211
232
|
const killed = commandRunner.kill(runId);
|
|
212
233
|
console.log(`[KILL] Kill result: ${killed} for runId: ${runId}`);
|
|
213
234
|
if (!killed) {
|
|
@@ -6,19 +6,19 @@ import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
|
|
|
6
6
|
import * as gitService from '../services/gitService.js';
|
|
7
7
|
import * as summaryService from '../services/summaryService.js';
|
|
8
8
|
import { executeHookAsync } from '../services/hookService.js';
|
|
9
|
-
import { requireSession, requireSessionAndProject } from '../middleware/sessionLookup.js';
|
|
9
|
+
import { requireRootSessionAndProject, requireSession, requireSessionAndProject } from '../middleware/sessionLookup.js';
|
|
10
10
|
import { duplicateSession } from '../services/sessionDuplicator.js';
|
|
11
11
|
import { configureSchedule, ScheduleError } from '../services/scheduleService.js';
|
|
12
12
|
|
|
13
13
|
const router = Router();
|
|
14
14
|
|
|
15
|
-
// GET /api/sessions/:id/summary - Get
|
|
16
|
-
router.get('/:id/summary',
|
|
15
|
+
// GET /api/sessions/:id/summary - Get workflow summary
|
|
16
|
+
router.get('/:id/summary', requireRootSessionAndProject, async (req, res) => {
|
|
17
17
|
// Check if generateIfMissing query param is set
|
|
18
18
|
const generateIfMissing = req.query.generate === 'true';
|
|
19
19
|
|
|
20
20
|
try {
|
|
21
|
-
const summary = await summaryService.getSummary(req.
|
|
21
|
+
const summary = await summaryService.getSummary(req.rootSessionId, generateIfMissing);
|
|
22
22
|
if (!summary) {
|
|
23
23
|
return res.status(404).json({ error: 'Summary not found' });
|
|
24
24
|
}
|
|
@@ -28,10 +28,10 @@ router.get('/:id/summary', requireSession, async (req, res) => {
|
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
// POST /api/sessions/:id/summary - Generate/regenerate
|
|
32
|
-
router.post('/:id/summary',
|
|
31
|
+
// POST /api/sessions/:id/summary - Generate/regenerate workflow summary
|
|
32
|
+
router.post('/:id/summary', requireRootSessionAndProject, async (req, res) => {
|
|
33
33
|
try {
|
|
34
|
-
const summary = await summaryService.regenerateSummary(req.
|
|
34
|
+
const summary = await summaryService.regenerateSummary(req.rootSessionId);
|
|
35
35
|
if (!summary) {
|
|
36
36
|
return res.status(500).json({ error: 'Failed to generate summary' });
|
|
37
37
|
}
|
|
@@ -41,10 +41,10 @@ router.post('/:id/summary', requireSession, async (req, res) => {
|
|
|
41
41
|
}
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
-
// PUT /api/sessions/:id/summary - Directly set summary data (for testing/seeding)
|
|
45
|
-
router.put('/:id/summary',
|
|
44
|
+
// PUT /api/sessions/:id/summary - Directly set workflow summary data (for testing/seeding)
|
|
45
|
+
router.put('/:id/summary', requireRootSessionAndProject, async (req, res) => {
|
|
46
46
|
try {
|
|
47
|
-
const summary = sessionSummaries.upsert(req.
|
|
47
|
+
const summary = sessionSummaries.upsert(req.rootSessionId, req.body);
|
|
48
48
|
res.json(summary);
|
|
49
49
|
} catch (error) {
|
|
50
50
|
res.status(500).json({ error: error.message });
|