circuschief 0.6.0 → 0.8.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/index.js +2 -0
- package/packages/server/src/api/kanban.js +4 -2
- package/packages/server/src/api/projects-helpers.js +20 -3
- package/packages/server/src/api/projects-session-helpers.js +35 -4
- package/packages/server/src/api/projects.js +11 -0
- package/packages/server/src/api/providers.js +11 -1
- package/packages/server/src/api/sessions-commands.js +35 -17
- package/packages/server/src/api/sessions-draft.js +1 -0
- package/packages/server/src/api/sessions-lifecycle.js +10 -10
- package/packages/server/src/api/sessions-patch.js +4 -0
- package/packages/server/src/api/sessions.js +6 -5
- package/packages/server/src/api/settings.js +52 -4
- package/packages/server/src/database.js +0 -2
- package/packages/server/src/db/ConversationRepository.js +16 -3
- package/packages/server/src/db/DatabaseManager.js +5 -1
- package/packages/server/src/db/ProjectDefaultsRepository.js +50 -40
- package/packages/server/src/db/ProviderRepository.js +87 -32
- package/packages/server/src/db/SessionRepository.js +13 -8
- package/packages/server/src/db/SettingsRepository.js +44 -16
- package/packages/server/src/db/conversation-helpers.js +1 -0
- package/packages/server/src/db/index.js +0 -3
- package/packages/server/src/db/migrations/index.js +36 -200
- package/packages/server/src/db/seedBaselineData.js +137 -0
- package/packages/server/src/db/session-helpers.js +9 -3
- package/packages/server/src/middleware/sessionLookup.js +81 -8
- package/packages/server/src/schema.sql +157 -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/agentCallLogger.js +1 -1
- package/packages/server/src/services/codexSpawnHelper.js +9 -0
- package/packages/server/src/services/commandButtonPrompts.js +48 -0
- package/packages/server/src/services/commandRunner.js +7 -1
- package/packages/server/src/services/draftSessionService.js +2 -0
- package/packages/server/src/services/e2eSpawnCapture.js +147 -0
- package/packages/server/src/services/gitCommitAttribution.js +120 -0
- package/packages/server/src/services/gitService.js +11 -2
- package/packages/server/src/services/gitSessionSetup.js +11 -1
- 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 -106
- package/packages/server/src/services/sessionPrompts.js +16 -47
- package/packages/server/src/services/sessionProvider.js +16 -8
- package/packages/server/src/services/streamEventCallbacks.js +72 -40
- package/packages/server/src/services/streamEventHandler.js +13 -2
- package/packages/server/src/services/streamUsageHandler.js +6 -0
- package/packages/server/src/services/summaryClaudeClient.js +37 -12
- package/packages/server/src/services/summaryModelClient.js +154 -0
- package/packages/server/src/services/summaryModelResolver.js +148 -0
- package/packages/server/src/services/summaryService.js +11 -7
- package/packages/server/src/services/summaryStaleCheck.js +23 -4
- package/packages/server/src/services/templateTriggerService.js +3 -1
- package/packages/server/src/services/usageTracker.js +5 -1
- package/packages/server/src/services/visibleFinalErrorMessage.js +123 -0
- package/packages/shared/src/constants.js +4 -2
- package/packages/shared/src/contracts/commandButtons.js +16 -2
- package/packages/shared/src/contracts/projects.js +4 -2
- package/packages/shared/src/contracts/providers.js +60 -0
- package/packages/shared/src/contracts/sessions.js +2 -1
- package/packages/shared/src/contracts/templates.js +2 -2
- package/packages/shared/src/types.js +1 -9
- package/packages/shared/src/utils.js +11 -19
- package/packages/web/dist/assets/ActiveSessionsView-B0XHqLmv.js +1 -0
- package/packages/web/dist/assets/{AgentLogsView-Cdw4nmvd.js → AgentLogsView-DmsjUMlB.js} +2 -2
- package/packages/web/dist/assets/ApiClient-C3ztI9s9.js +1 -0
- package/packages/web/dist/assets/ArchiveConfirmModal-BlCyn5Vt.js +1 -0
- package/packages/web/dist/assets/ArchiveConfirmModal-DeoCVGXt.css +1 -0
- package/packages/web/dist/assets/{CommandButtonDetailView-DnFhJY5A.js → CommandButtonDetailView-CdSCPp78.js} +1 -1
- package/packages/web/dist/assets/EffortLevelSelector-hc2MNKg6.js +1 -0
- package/packages/web/dist/assets/{GeneralSettingsView-CQkmdczf.js → GeneralSettingsView-D1nI8_zk.js} +1 -1
- package/packages/web/dist/assets/InputWithButton-CAkttyqx.js +1 -0
- package/packages/web/dist/assets/{InputWithButton-cYdrEmTs.css → InputWithButton-D9HMvfR5.css} +1 -1
- package/packages/web/dist/assets/{InterpolationHelp-PfYR3KJo.js → InterpolationHelp-BO1j9Z3_.js} +1 -1
- package/packages/web/dist/assets/MarkdownEditor-ucRAP_UM.js +2 -0
- package/packages/web/dist/assets/{ModelSelector-BZOT1Jc6.css → ModelSelector-BSxKUSus.css} +1 -1
- package/packages/web/dist/assets/ModelSelector-CwTz8ZWO.js +1 -0
- package/packages/web/dist/assets/NewSessionView-BDPb-1qr.css +1 -0
- package/packages/web/dist/assets/NewSessionView-BsDrp8mj.js +3 -0
- package/packages/web/dist/assets/ProjectEditView-CwTOeSun.js +1 -0
- package/packages/web/dist/assets/ProjectEditView-J15mcsWz.css +1 -0
- package/packages/web/dist/assets/{ProjectListView-CuYMmd3O.js → ProjectListView-DcNyuINs.js} +1 -1
- package/packages/web/dist/assets/{ProjectNewView-CNaA4Maf.js → ProjectNewView-B5YV62hv.js} +1 -1
- package/packages/web/dist/assets/ProvidersView-bZemq_Rv.css +1 -0
- package/packages/web/dist/assets/ProvidersView-nY9GnDdO.js +1 -0
- package/packages/web/dist/assets/QuickResponseSettings-B352c75l.css +1 -0
- package/packages/web/dist/assets/QuickResponseSettings-BQwQXuL7.js +1 -0
- package/packages/web/dist/assets/{QuickResponsesPanel-DIBQFj0W.css → QuickResponsesPanel-BlFDvnZ2.css} +1 -1
- package/packages/web/dist/assets/{QuickResponsesPanel-BqMYSHb0.js → QuickResponsesPanel-BzSYcCSP.js} +1 -1
- package/packages/web/dist/assets/{ResizableTextarea-wYF3K2RO.js → ResizableTextarea-B3YIdIXv.js} +1 -1
- package/packages/web/dist/assets/{SessionCard-bLaQEWWX.js → SessionCard-CjE1tXiT.js} +1 -1
- package/packages/web/dist/assets/SessionDetailView-3cPZrbS3.js +36 -0
- package/packages/web/dist/assets/SessionDetailView-CZRZMrfM.css +1 -0
- package/packages/web/dist/assets/SessionFormOptions-B6AxyREh.js +1 -0
- package/packages/web/dist/assets/SessionFormOptions-BpUALRKn.css +1 -0
- package/packages/web/dist/assets/SessionListView-B5_6gW49.css +1 -0
- package/packages/web/dist/assets/SessionListView-CLXBfLcq.js +1 -0
- package/packages/web/dist/assets/{SessionLogStream-DTnDAF95.js → SessionLogStream-LlZ3z_Xj.js} +1 -1
- package/packages/web/dist/assets/{SettingsView-DNLUSsHV.js → SettingsView-CTGiGvR2.js} +1 -1
- package/packages/web/dist/assets/{SlashCommandWizard-CRGFaO8t.js → SlashCommandWizard-Cy04d7-o.js} +1 -1
- package/packages/web/dist/assets/{SlashCommandWizard-Dn7sNaBd.css → SlashCommandWizard-DJzw3LP5.css} +1 -1
- package/packages/web/dist/assets/SummarySettingsView-BR2ZjEa3.js +1 -0
- package/packages/web/dist/assets/SummarySettingsView-l2bxHmZZ.css +1 -0
- package/packages/web/dist/assets/TemplateDetailView-DH6Oswsp.js +1 -0
- package/packages/web/dist/assets/{commandButtons-Bbjf3fCt.js → commandButtons-BfqR-fqq.js} +1 -1
- package/packages/web/dist/assets/index-1zziPL6l.js +1 -0
- package/packages/web/dist/assets/index-7kzHPxSF.js +1 -0
- package/packages/web/dist/assets/index-B0N_obMc.js +1 -0
- package/packages/web/dist/assets/index-BNk_gdfI.js +1 -0
- package/packages/web/dist/assets/{index-gmCCsCQ1.css → index-BY174HVJ.css} +1 -1
- package/packages/web/dist/assets/index-CSqaAH-0.js +1 -0
- package/packages/web/dist/assets/index-C_q4WlK8.js +1 -0
- package/packages/web/dist/assets/index-D1wpU4y0.js +7 -0
- package/packages/web/dist/assets/index-D5zCA8sD.js +1 -0
- package/packages/web/dist/assets/index-DGR8ELWY.js +1 -0
- package/packages/web/dist/assets/index-DHga8pXo.js +1 -0
- package/packages/web/dist/assets/index-DSby02Wl.js +1 -0
- package/packages/web/dist/assets/{index-Cf6vdW-B.js → index-DgkC10TW.js} +3 -3
- package/packages/web/dist/assets/index-DqjXJTVI.js +1 -0
- package/packages/web/dist/assets/{index-Bs7Qf5D6.js → index-DtfUt785.js} +2 -2
- package/packages/web/dist/assets/index-_4S2uLDI.js +1 -0
- package/packages/web/dist/assets/{index-BQL_L4gL.js → index-fK8FIZgP.js} +15 -14
- package/packages/web/dist/assets/index-gmiZeFXN.js +1 -0
- package/packages/web/dist/assets/index-irD539ZM.js +3 -0
- package/packages/web/dist/assets/index-yq-E1Y00.js +1 -0
- package/packages/web/dist/assets/{projects-CPt3AB7U.js → projects-DXYQNJIi.js} +1 -1
- package/packages/web/dist/assets/{providers-ChfeMvUq.js → providers-1bnH-exJ.js} +1 -1
- package/packages/web/dist/assets/sessions-6zGUlFrt.js +1 -0
- package/packages/web/dist/assets/settings-MbfRir0d.js +1 -0
- 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/server/src/db/migrations/canvasItemsMigrations.js +0 -109
- package/packages/server/src/db/migrations/conversationsMigrations.js +0 -183
- package/packages/server/src/db/migrations/kanbanMigrations.js +0 -99
- package/packages/server/src/db/migrations/miscMigrations.js +0 -369
- package/packages/server/src/db/migrations/projectsMigrations.js +0 -99
- package/packages/server/src/db/migrations/sessionsMigrations.js +0 -282
- package/packages/web/dist/assets/ActiveSessionsView-UCbQrF1b.js +0 -1
- package/packages/web/dist/assets/ApiClient-CWbXWDUY.js +0 -1
- package/packages/web/dist/assets/ArchiveConfirmModal-BQ-4gI0R.css +0 -1
- package/packages/web/dist/assets/ArchiveConfirmModal-J48eh3zw.js +0 -1
- package/packages/web/dist/assets/EffortLevelSelector-bXbPo4Zw.js +0 -1
- package/packages/web/dist/assets/InputWithButton-XyM3k6lN.js +0 -1
- package/packages/web/dist/assets/MarkdownEditor-P8F5kO-o.js +0 -2
- package/packages/web/dist/assets/ModelSelector-CowKfGMP.js +0 -1
- package/packages/web/dist/assets/NewSessionView-D_Hi7M9g.css +0 -1
- package/packages/web/dist/assets/NewSessionView-DkjFLvHU.js +0 -3
- package/packages/web/dist/assets/ProjectEditView-CpeKj-_w.css +0 -1
- package/packages/web/dist/assets/ProjectEditView-embVT7NC.js +0 -1
- package/packages/web/dist/assets/ProvidersView-C7rydtOd.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-BTQEKhwJ.js +0 -1
- package/packages/web/dist/assets/SessionDetailView-Cv-xMzXp.css +0 -1
- package/packages/web/dist/assets/SessionDetailView-CvQOUsW2.js +0 -36
- package/packages/web/dist/assets/SessionFormOptions-3pzbgI2Q.js +0 -1
- package/packages/web/dist/assets/SessionFormOptions-DhhIkjIS.css +0 -1
- package/packages/web/dist/assets/SessionListView-Dranfb72.js +0 -1
- package/packages/web/dist/assets/SessionListView-fHlQyecX.css +0 -1
- package/packages/web/dist/assets/SummarySettingsView-C7G_suHp.js +0 -1
- package/packages/web/dist/assets/SummarySettingsView-DcsmSVJI.css +0 -1
- package/packages/web/dist/assets/TemplateDetailView-B78_DLMR.js +0 -1
- package/packages/web/dist/assets/index--V7c-VZf.js +0 -1
- package/packages/web/dist/assets/index-8Q04yd7H.js +0 -1
- package/packages/web/dist/assets/index-B47XRBDH.js +0 -1
- package/packages/web/dist/assets/index-BXbgZrhS.js +0 -1
- package/packages/web/dist/assets/index-CGhDVPen.js +0 -1
- package/packages/web/dist/assets/index-CKcRO1A6.js +0 -1
- package/packages/web/dist/assets/index-CTq-SLIW.js +0 -1
- package/packages/web/dist/assets/index-CYyos3iC.js +0 -1
- package/packages/web/dist/assets/index-CsCREAxF.js +0 -1
- package/packages/web/dist/assets/index-DJTTk_8T.js +0 -3
- package/packages/web/dist/assets/index-DPqUJ5JK.js +0 -1
- package/packages/web/dist/assets/index-EwAe1dKg.js +0 -1
- package/packages/web/dist/assets/index-JBA8axyA.js +0 -1
- package/packages/web/dist/assets/index-JkVHFtK5.js +0 -7
- package/packages/web/dist/assets/index-gMPUwT55.js +0 -1
- package/packages/web/dist/assets/index-wadc_0zT.js +0 -1
- package/packages/web/dist/assets/sessions-CwPsJOb1.js +0 -1
- package/packages/web/dist/assets/settings-BOj6wq6t.js +0 -1
package/package.json
CHANGED
|
@@ -14,16 +14,17 @@ let codexCliUnavailable = false;
|
|
|
14
14
|
*
|
|
15
15
|
* Two execution paths:
|
|
16
16
|
*
|
|
17
|
-
* 1. CLI path (default) — spawns
|
|
17
|
+
* 1. CLI path (default) — spawns `codex exec --json ...` and parses its
|
|
18
18
|
* line-delimited JSON stdout. Uses {@link createCodexEventMapper} to
|
|
19
19
|
* normalize events into the SDK-shaped envelope the rest of the app
|
|
20
20
|
* already understands.
|
|
21
21
|
*
|
|
22
22
|
* 2. Direct-API path — activated by {@code USE_CODEX_DIRECT_API=1}. Uses
|
|
23
23
|
* the official {@code openai} SDK with Chat Completions streaming
|
|
24
|
-
* against the provider's configured baseURL/apiKey.
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* against the provider's configured baseURL/apiKey. It bypasses
|
|
25
|
+
* CLI-specific behavior such as sandbox enforcement and Codex
|
|
26
|
+
* commit-attribution config. Intended for
|
|
27
|
+
* environments where the Codex CLI isn't installable.
|
|
27
28
|
*
|
|
28
29
|
* Capabilities in v1:
|
|
29
30
|
* - streaming: true
|
|
@@ -2,10 +2,11 @@ import { Router } from 'express';
|
|
|
2
2
|
import { readFileSync, existsSync, statSync } from 'fs';
|
|
3
3
|
import { mkdir } from 'fs/promises';
|
|
4
4
|
import { extname, join, basename } from 'path';
|
|
5
|
-
import {
|
|
5
|
+
import { canvasItems } from '../database.js';
|
|
6
6
|
import { broadcastToSession } from '../websocket.js';
|
|
7
7
|
import { WS_MESSAGE_TYPES, UpdateCanvasItemRequest } from '../../../shared/src/index.js';
|
|
8
8
|
import { upload, handleUploadError } from '../middleware/upload.js';
|
|
9
|
+
import { requireRootSessionAndProject } from '../middleware/sessionLookup.js';
|
|
9
10
|
import {
|
|
10
11
|
isBinaryContent,
|
|
11
12
|
getTypeFromExtension,
|
|
@@ -19,8 +20,6 @@ import {
|
|
|
19
20
|
import trashRoutes from './canvas-trash-routes.js';
|
|
20
21
|
|
|
21
22
|
// Error message constants
|
|
22
|
-
const ERR_SESSION_NOT_FOUND = 'Session not found';
|
|
23
|
-
|
|
24
23
|
/**
|
|
25
24
|
* Process multipart file upload (Mode 1)
|
|
26
25
|
* @param {Express.Multer.File} file - The uploaded file from multer
|
|
@@ -84,12 +83,7 @@ router.use('/', trashRoutes);
|
|
|
84
83
|
// 1. Multipart mode: FormData with 'file' field - from browser file uploads
|
|
85
84
|
// 2. File mode: { filePath } - reads file from disk
|
|
86
85
|
// 3. Inline mode: { type, content, filename } - uses provided content directly
|
|
87
|
-
router.post('/:id/canvas', upload.single('file'), handleUploadError, (req, res) => {
|
|
88
|
-
const session = sessions.getById(req.params.id);
|
|
89
|
-
if (!session) {
|
|
90
|
-
return res.status(404).json({ error: ERR_SESSION_NOT_FOUND });
|
|
91
|
-
}
|
|
92
|
-
|
|
86
|
+
router.post('/:id/canvas', requireRootSessionAndProject, upload.single('file'), handleUploadError, (req, res) => {
|
|
93
87
|
const { filePath, type, content, filename } = req.body;
|
|
94
88
|
let result;
|
|
95
89
|
|
|
@@ -110,21 +104,16 @@ router.post('/:id/canvas', upload.single('file'), handleUploadError, (req, res)
|
|
|
110
104
|
return res.status(400).json({ error: result.error });
|
|
111
105
|
}
|
|
112
106
|
|
|
113
|
-
const item = canvasItems.create(req.
|
|
107
|
+
const item = canvasItems.create(req.rootSessionId, result.itemData);
|
|
114
108
|
|
|
115
109
|
// Broadcast to session subscribers
|
|
116
|
-
broadcastCanvasUpdate(req.
|
|
110
|
+
broadcastCanvasUpdate(req.rootSessionId, item);
|
|
117
111
|
|
|
118
112
|
res.status(201).json(item);
|
|
119
113
|
});
|
|
120
114
|
|
|
121
115
|
// PUT /api/sessions/:id/canvas/:itemId - Update canvas item content in-place
|
|
122
|
-
router.put('/:id/canvas/:itemId', (req, res) => {
|
|
123
|
-
const session = sessions.getById(req.params.id);
|
|
124
|
-
if (!session) {
|
|
125
|
-
return res.status(404).json({ error: ERR_SESSION_NOT_FOUND });
|
|
126
|
-
}
|
|
127
|
-
|
|
116
|
+
router.put('/:id/canvas/:itemId', requireRootSessionAndProject, (req, res) => {
|
|
128
117
|
const parsed = UpdateCanvasItemRequest.safeParse(req.body);
|
|
129
118
|
if (!parsed.success) {
|
|
130
119
|
return res.status(400).json({ error: 'Invalid request body', details: parsed.error.issues });
|
|
@@ -135,7 +124,7 @@ router.put('/:id/canvas/:itemId', (req, res) => {
|
|
|
135
124
|
return res.status(404).json({ error: 'Canvas item not found' });
|
|
136
125
|
}
|
|
137
126
|
|
|
138
|
-
if (item.sessionId !== req.
|
|
127
|
+
if (item.sessionId !== req.rootSessionId) {
|
|
139
128
|
return res.status(400).json({ error: 'Canvas item does not belong to this session' });
|
|
140
129
|
}
|
|
141
130
|
|
|
@@ -144,20 +133,15 @@ router.put('/:id/canvas/:itemId', (req, res) => {
|
|
|
144
133
|
}
|
|
145
134
|
|
|
146
135
|
const updatedItem = canvasItems.updateContent(req.params.itemId, parsed.data.content);
|
|
147
|
-
broadcastToSession(req.
|
|
136
|
+
broadcastToSession(req.rootSessionId, WS_MESSAGE_TYPES.CANVAS_UPDATE, { item: updatedItem });
|
|
148
137
|
res.json(updatedItem);
|
|
149
138
|
});
|
|
150
139
|
|
|
151
140
|
// GET /api/sessions/:id/canvas - List canvas items
|
|
152
141
|
// Returns only latest version of each file (no version metadata exposed)
|
|
153
|
-
router.get('/:id/canvas', (req, res) => {
|
|
154
|
-
const session = sessions.getById(req.params.id);
|
|
155
|
-
if (!session) {
|
|
156
|
-
return res.status(404).json({ error: ERR_SESSION_NOT_FOUND });
|
|
157
|
-
}
|
|
158
|
-
|
|
142
|
+
router.get('/:id/canvas', requireRootSessionAndProject, (req, res) => {
|
|
159
143
|
// Get only latest versions (one per filename)
|
|
160
|
-
const items = canvasItems.getLatestVersionsBySessionId(req.
|
|
144
|
+
const items = canvasItems.getLatestVersionsBySessionId(req.rootSessionId);
|
|
161
145
|
|
|
162
146
|
// Strip content/data from list responses to reduce payload size.
|
|
163
147
|
// Clients should use GET /canvas/file/:filename/content for inline content.
|
|
@@ -166,14 +150,9 @@ router.get('/:id/canvas', (req, res) => {
|
|
|
166
150
|
|
|
167
151
|
// GET /api/sessions/:id/canvas/all - List ALL canvas items (including all versions)
|
|
168
152
|
// Returns all versions of each file for the frontend UI
|
|
169
|
-
router.get('/:id/canvas/all', (req, res) => {
|
|
170
|
-
const session = sessions.getById(req.params.id);
|
|
171
|
-
if (!session) {
|
|
172
|
-
return res.status(404).json({ error: ERR_SESSION_NOT_FOUND });
|
|
173
|
-
}
|
|
174
|
-
|
|
153
|
+
router.get('/:id/canvas/all', requireRootSessionAndProject, (req, res) => {
|
|
175
154
|
// Get ALL versions (not just latest)
|
|
176
|
-
const items = canvasItems.getBySessionId(req.
|
|
155
|
+
const items = canvasItems.getBySessionId(req.rootSessionId);
|
|
177
156
|
|
|
178
157
|
// Strip content/data from list responses to reduce payload size.
|
|
179
158
|
res.json(items.map(({ content: _content, data: _data, ...meta }) => meta));
|
|
@@ -182,15 +161,11 @@ router.get('/:id/canvas/all', (req, res) => {
|
|
|
182
161
|
// GET /api/sessions/:id/canvas/file/:filename/history/:version - Get historical version of canvas file
|
|
183
162
|
// Uses standard versioning: version 1 = oldest, version N = latest (where N = totalVersions)
|
|
184
163
|
// NOTE: This route must be defined BEFORE the main file route to match correctly
|
|
185
|
-
router.get('/:id/canvas/file/:filename/history/:version', async (req, res) => {
|
|
186
|
-
const {
|
|
164
|
+
router.get('/:id/canvas/file/:filename/history/:version', requireRootSessionAndProject, async (req, res) => {
|
|
165
|
+
const { filename, version } = req.params;
|
|
166
|
+
const sessionId = req.rootSessionId;
|
|
187
167
|
const versionNum = parseInt(version, 10);
|
|
188
168
|
|
|
189
|
-
const session = sessions.getById(sessionId);
|
|
190
|
-
if (!session) {
|
|
191
|
-
return res.status(404).json({ error: ERR_SESSION_NOT_FOUND });
|
|
192
|
-
}
|
|
193
|
-
|
|
194
169
|
// Get all versions of this file (newest first)
|
|
195
170
|
const allVersions = canvasItems.getAllVersionsByFilename(sessionId, filename);
|
|
196
171
|
if (allVersions.length === 0) {
|
|
@@ -247,11 +222,8 @@ router.get('/:id/canvas/file/:filename/history/:version', async (req, res) => {
|
|
|
247
222
|
// GET /api/sessions/:id/canvas/file/:filename/content - Get canvas file content inline
|
|
248
223
|
// Returns content/data inline in JSON for browser consumption (unlike the temp-file endpoint below)
|
|
249
224
|
// Supports ?version=N query param (1-based, 1 = oldest)
|
|
250
|
-
router.get('/:id/canvas/file/:filename/content', (req, res) => {
|
|
251
|
-
const
|
|
252
|
-
if (!session) return res.status(404).json({ error: ERR_SESSION_NOT_FOUND });
|
|
253
|
-
|
|
254
|
-
const allVersions = canvasItems.getAllVersionsByFilename(req.params.id, req.params.filename);
|
|
225
|
+
router.get('/:id/canvas/file/:filename/content', requireRootSessionAndProject, (req, res) => {
|
|
226
|
+
const allVersions = canvasItems.getAllVersionsByFilename(req.rootSessionId, req.params.filename);
|
|
255
227
|
if (allVersions.length === 0) return res.status(404).json({ error: 'File not found' });
|
|
256
228
|
|
|
257
229
|
// Support ?version=N (1-based, 1 = oldest)
|
|
@@ -280,13 +252,10 @@ router.get('/:id/canvas/file/:filename/content', (req, res) => {
|
|
|
280
252
|
});
|
|
281
253
|
|
|
282
254
|
// GET /api/sessions/:id/canvas/:itemId/content - Get one canvas item content inline
|
|
283
|
-
router.get('/:id/canvas/:itemId/content', (req, res) => {
|
|
284
|
-
const session = sessions.getById(req.params.id);
|
|
285
|
-
if (!session) return res.status(404).json({ error: ERR_SESSION_NOT_FOUND });
|
|
286
|
-
|
|
255
|
+
router.get('/:id/canvas/:itemId/content', requireRootSessionAndProject, (req, res) => {
|
|
287
256
|
const item = canvasItems.getById(req.params.itemId);
|
|
288
257
|
if (!item) return res.status(404).json({ error: 'Canvas item not found' });
|
|
289
|
-
if (item.sessionId !== req.
|
|
258
|
+
if (item.sessionId !== req.rootSessionId) {
|
|
290
259
|
return res.status(400).json({ error: 'Canvas item does not belong to this session' });
|
|
291
260
|
}
|
|
292
261
|
|
|
@@ -302,13 +271,9 @@ router.get('/:id/canvas/:itemId/content', (req, res) => {
|
|
|
302
271
|
// GET /api/sessions/:id/canvas/file/:filename - Get canvas file by filename
|
|
303
272
|
// Writes the file to /tmp and returns the file path for Claude's Read tool
|
|
304
273
|
// Always returns the latest version
|
|
305
|
-
router.get('/:id/canvas/file/:filename', async (req, res) => {
|
|
306
|
-
const {
|
|
307
|
-
|
|
308
|
-
const session = sessions.getById(sessionId);
|
|
309
|
-
if (!session) {
|
|
310
|
-
return res.status(404).json({ error: ERR_SESSION_NOT_FOUND });
|
|
311
|
-
}
|
|
274
|
+
router.get('/:id/canvas/file/:filename', requireRootSessionAndProject, async (req, res) => {
|
|
275
|
+
const { filename } = req.params;
|
|
276
|
+
const sessionId = req.rootSessionId;
|
|
312
277
|
|
|
313
278
|
// Get all versions of this file (newest first)
|
|
314
279
|
const allVersions = canvasItems.getAllVersionsByFilename(sessionId, filename);
|
|
@@ -14,6 +14,7 @@ import kanbanRouter from './kanban.js';
|
|
|
14
14
|
import agentsRouter from './agents.js';
|
|
15
15
|
import { getDbPath } from '../database.js';
|
|
16
16
|
import { schedulerService } from '../services/schedulerService.js';
|
|
17
|
+
import { isE2ESpawnCaptureEnabled } from '../services/e2eSpawnCapture.js';
|
|
17
18
|
|
|
18
19
|
const router = Router();
|
|
19
20
|
|
|
@@ -29,6 +30,7 @@ router.get('/server-info', (_req, res) => {
|
|
|
29
30
|
dbPath: getDbPath(),
|
|
30
31
|
vcrMode: vcr && vcr.length > 0 ? vcr : null,
|
|
31
32
|
schedulerRunning: schedulerService.isRunning(),
|
|
33
|
+
e2eSpawnCaptureEnabled: isE2ESpawnCaptureEnabled(),
|
|
32
34
|
});
|
|
33
35
|
});
|
|
34
36
|
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
ReorderKanbanCardsRequest,
|
|
12
12
|
} from '../../../shared/src/contracts/kanban.js';
|
|
13
13
|
import { moveCard as moveCardService } from '../services/kanbanService.js';
|
|
14
|
+
import { resolveBodyRootSessionForProject } from '../middleware/sessionLookup.js';
|
|
14
15
|
|
|
15
16
|
const router = Router({ mergeParams: true });
|
|
16
17
|
|
|
@@ -215,7 +216,7 @@ router.put('/lanes/reorder', (req, res) => {
|
|
|
215
216
|
* POST /api/projects/:projectId/kanban/cards
|
|
216
217
|
* Add a session to the board (create card in a lane)
|
|
217
218
|
*/
|
|
218
|
-
router.post('/cards', (req, res) => {
|
|
219
|
+
router.post('/cards', resolveBodyRootSessionForProject('projectId'), (req, res) => {
|
|
219
220
|
const { projectId } = req.params;
|
|
220
221
|
|
|
221
222
|
const result = CreateKanbanCardRequest.safeParse(req.body);
|
|
@@ -223,7 +224,8 @@ router.post('/cards', (req, res) => {
|
|
|
223
224
|
return res.status(400).json({ error: result.error.issues[0].message });
|
|
224
225
|
}
|
|
225
226
|
|
|
226
|
-
const {
|
|
227
|
+
const { laneId } = result.data;
|
|
228
|
+
const sessionId = req.bodyRootSessionId;
|
|
227
229
|
|
|
228
230
|
// Check if session already has a card
|
|
229
231
|
const existingCard = kanbanCards.getBySessionId(sessionId);
|
|
@@ -1,22 +1,39 @@
|
|
|
1
|
+
import { generateWorktreeBranch } from '../../../shared/src/index.js';
|
|
1
2
|
import { isGitRepo } from '../services/gitService.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Validate and default git settings for git-backed projects.
|
|
5
6
|
* If gitMode or gitBranch are missing for a git project, defaults are applied
|
|
6
|
-
*
|
|
7
|
+
* instead of rejecting the request. Worktree sessions receive a generated branch
|
|
8
|
+
* so they never try to check out an already-active branch such as main.
|
|
9
|
+
* Current-mode sessions skip branch generation entirely.
|
|
7
10
|
* @param {Object} config - The session configuration
|
|
8
11
|
* @param {Object} project - The project object
|
|
9
12
|
* @returns {Promise<{config: Object, error: string|null}>} Updated config and error message if validation fails.
|
|
10
13
|
*/
|
|
11
14
|
export async function validateGitSettings(config, project) {
|
|
15
|
+
// Current mode: no branch needed, pass through as-is
|
|
16
|
+
if (config.gitMode === 'current') {
|
|
17
|
+
return {
|
|
18
|
+
config: {
|
|
19
|
+
...config,
|
|
20
|
+
gitBranch: null,
|
|
21
|
+
},
|
|
22
|
+
error: null,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
12
26
|
if (!config.gitMode || !config.gitBranch) {
|
|
13
27
|
const isGit = await isGitRepo(project.workingDirectory);
|
|
14
28
|
if (isGit) {
|
|
29
|
+
const gitMode = config.gitMode || 'none';
|
|
15
30
|
return {
|
|
16
31
|
config: {
|
|
17
32
|
...config,
|
|
18
|
-
gitMode
|
|
19
|
-
gitBranch: config.gitBranch || '
|
|
33
|
+
gitMode,
|
|
34
|
+
gitBranch: config.gitBranch || (gitMode === 'worktree'
|
|
35
|
+
? generateWorktreeBranch(config.name, config.prompt)
|
|
36
|
+
: 'main'),
|
|
20
37
|
},
|
|
21
38
|
error: null,
|
|
22
39
|
};
|
|
@@ -1,9 +1,10 @@
|
|
|
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';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Generate an initial session name from the prompt
|
|
@@ -44,6 +45,26 @@ export function resolveDefault(explicit, projectDefault, systemDefault) {
|
|
|
44
45
|
return systemDefault;
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Normalize provider IDs from JSON or multipart request fields.
|
|
50
|
+
* @param {*} value - Raw providerId value
|
|
51
|
+
* @returns {string|null|undefined}
|
|
52
|
+
*/
|
|
53
|
+
export function normalizeProviderId(value) {
|
|
54
|
+
if (value === undefined) return undefined;
|
|
55
|
+
if (value === null || value === '') return null;
|
|
56
|
+
if (typeof value !== 'string') {
|
|
57
|
+
throw new TypeError('providerId must be a string or null');
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function resolveProviderDefault(explicit, projectDefault, systemDefault) {
|
|
63
|
+
if (explicit !== undefined) return explicit;
|
|
64
|
+
if (projectDefault !== undefined && projectDefault !== null) return projectDefault;
|
|
65
|
+
return systemDefault ?? null;
|
|
66
|
+
}
|
|
67
|
+
|
|
47
68
|
/**
|
|
48
69
|
* Resolve thinkingEnabled with special boolean handling.
|
|
49
70
|
* @param {object} body - Request body
|
|
@@ -71,7 +92,7 @@ export function parseSchedulingConfig(body) {
|
|
|
71
92
|
return {
|
|
72
93
|
scheduledAt: body.scheduledAt ? parseInt(body.scheduledAt, 10) : undefined,
|
|
73
94
|
autoRescheduleEnabled: body.autoRescheduleEnabled === true || body.autoRescheduleEnabled === 'true',
|
|
74
|
-
rescheduleDelayMinutes: body.rescheduleDelayMinutes ? parseInt(body.rescheduleDelayMinutes, 10) :
|
|
95
|
+
rescheduleDelayMinutes: body.rescheduleDelayMinutes ? parseInt(body.rescheduleDelayMinutes, 10) : DEFAULT_RESCHEDULE_DELAY_MINUTES,
|
|
75
96
|
rescheduleOnTokenLimit: body.rescheduleOnTokenLimit !== false && body.rescheduleOnTokenLimit !== 'false',
|
|
76
97
|
rescheduleOnServiceError: body.rescheduleOnServiceError !== false && body.rescheduleOnServiceError !== 'false',
|
|
77
98
|
maxRescheduleCount: body.maxRescheduleCount ? parseInt(body.maxRescheduleCount, 10) : null,
|
|
@@ -114,14 +135,19 @@ export function prepareSessionConfig(body, projectDefs, systemDefaults) {
|
|
|
114
135
|
effortLevel = null;
|
|
115
136
|
}
|
|
116
137
|
|
|
138
|
+
const explicitProviderId = normalizeProviderId(body.providerId);
|
|
139
|
+
const projectProviderId = normalizeProviderId(projectDefs?.providerId);
|
|
140
|
+
const systemProviderId = normalizeProviderId(systemDefaults.providerId);
|
|
141
|
+
|
|
117
142
|
return {
|
|
118
143
|
prompt: body.prompt,
|
|
119
144
|
name: body.name,
|
|
120
145
|
mode: resolveDefault(body.mode, projectDefs?.mode, systemDefaults.mode),
|
|
121
146
|
model: resolveDefault(body.model, projectDefs?.model, systemDefaults.model || null),
|
|
147
|
+
providerId: resolveProviderDefault(explicitProviderId, projectProviderId, systemProviderId),
|
|
122
148
|
effortLevel,
|
|
123
149
|
gitBranch: resolveDefault(body.gitBranch, projectDefs?.gitBranch, null),
|
|
124
|
-
gitMode: resolveDefault(body.gitMode, projectDefs?.gitMode,
|
|
150
|
+
gitMode: resolveDefault(body.gitMode, projectDefs?.gitMode, systemDefaults.gitMode),
|
|
125
151
|
templateId: body.templateId,
|
|
126
152
|
parentSessionId: body.parentSessionId || null,
|
|
127
153
|
files: [],
|
|
@@ -235,12 +261,17 @@ async function resolveSessionWorkingDirectory({ session, config, project }) {
|
|
|
235
261
|
};
|
|
236
262
|
}
|
|
237
263
|
|
|
264
|
+
// Normalize 'current' mode to null (no git isolation) for setupGitForSession
|
|
265
|
+
const normalizedGitMode = (config.gitMode === 'current') ? null : (config.gitMode || null);
|
|
266
|
+
|
|
238
267
|
const gitSetup = await setupGitForSession({
|
|
239
268
|
projectDir: project.workingDirectory,
|
|
240
|
-
gitMode:
|
|
269
|
+
gitMode: normalizedGitMode,
|
|
241
270
|
gitBranch: config.gitBranch || null,
|
|
242
271
|
sessionId: session.id,
|
|
243
272
|
worktreeBasePath: project.worktreePath || null,
|
|
273
|
+
commitAttributionOverride:
|
|
274
|
+
resolveProviderMetadataFromModel(config.model)?.commitAttributionOverride ?? null,
|
|
244
275
|
});
|
|
245
276
|
return { workingDirectory: gitSetup.workingDirectory, gitWorktree: gitSetup.gitWorktree };
|
|
246
277
|
}
|
|
@@ -212,6 +212,16 @@ async function validateAndPrepareSessionConfig(reqBody, reqFiles, projectId, pro
|
|
|
212
212
|
return { error: 'Prompt is required', status: 400 };
|
|
213
213
|
}
|
|
214
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
|
+
|
|
215
225
|
// Apply template overrides and resolve nextTemplateId
|
|
216
226
|
applyTemplateOverrides(config);
|
|
217
227
|
const { nextTemplateId, error: nextTemplateError } = resolveNextTemplateId(reqBody, config.nextTemplateId || null);
|
|
@@ -243,6 +253,7 @@ function createSessionRow(projectId, config, nextTemplateId, initialStatus) {
|
|
|
243
253
|
parentSessionId: config.parentSessionId,
|
|
244
254
|
status: initialStatus,
|
|
245
255
|
model: config.model,
|
|
256
|
+
providerId: config.providerId,
|
|
246
257
|
effortLevel: config.effortLevel,
|
|
247
258
|
agentType: config.agentType,
|
|
248
259
|
});
|
|
@@ -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,7 +2,7 @@ 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
|
|
|
@@ -46,9 +46,14 @@ function broadcastCommandError(ctx, errorMessage) {
|
|
|
46
46
|
broadcastToProject(projectId, WS_MESSAGE_TYPES.COMMAND_RUN_ERROR, { projectId, sessionId, runId, buttonId, error: errorMessage });
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
// GET /api/sessions/:id/command-buttons - List command buttons for the workflow project
|
|
50
|
+
router.get('/:id/command-buttons', requireRootSessionAndProject, (req, res) => {
|
|
51
|
+
res.json(commandButtons.getByProjectId(req.rootSession_.projectId));
|
|
52
|
+
});
|
|
53
|
+
|
|
49
54
|
// POST /api/sessions/:id/command-buttons/:buttonId/run - Execute button command
|
|
50
|
-
router.post('/:id/command-buttons/:buttonId/run',
|
|
51
|
-
const sessionId = req.
|
|
55
|
+
router.post('/:id/command-buttons/:buttonId/run', requireRootSessionAndProject, (req, res) => {
|
|
56
|
+
const sessionId = req.rootSessionId;
|
|
52
57
|
const buttonId = req.params.buttonId;
|
|
53
58
|
|
|
54
59
|
console.log(`[RUN] Starting command for buttonId: ${buttonId}, sessionId: ${sessionId}`);
|
|
@@ -57,6 +62,9 @@ router.post('/:id/command-buttons/:buttonId/run', requireSessionAndProject, (req
|
|
|
57
62
|
if (!button) {
|
|
58
63
|
return res.status(404).json({ error: 'Command button not found' });
|
|
59
64
|
}
|
|
65
|
+
if (button.projectId !== req.rootSession_.projectId) {
|
|
66
|
+
return res.status(404).json({ error: 'Command button not found' });
|
|
67
|
+
}
|
|
60
68
|
|
|
61
69
|
// Generate run ID
|
|
62
70
|
const runId = databaseManager.generateId();
|
|
@@ -67,8 +75,8 @@ router.post('/:id/command-buttons/:buttonId/run', requireSessionAndProject, (req
|
|
|
67
75
|
res.json({ runId, buttonId, status: 'running', output: '' });
|
|
68
76
|
|
|
69
77
|
// Capture middleware values for use in async callbacks
|
|
70
|
-
const projectId = req.
|
|
71
|
-
const workingDirectory = req.
|
|
78
|
+
const projectId = req.rootSession_.projectId;
|
|
79
|
+
const workingDirectory = req.rootWorkingDirectory;
|
|
72
80
|
const ctx = { sessionId, projectId, runId, buttonId };
|
|
73
81
|
|
|
74
82
|
// Broadcast initial "running" status immediately so session list can show the running indicator
|
|
@@ -98,16 +106,17 @@ router.post('/:id/command-buttons/:buttonId/run', requireSessionAndProject, (req
|
|
|
98
106
|
});
|
|
99
107
|
|
|
100
108
|
// GET /api/sessions/:id/command-buttons/runs - Get active runs for session
|
|
101
|
-
router.get('/:id/command-buttons/runs',
|
|
102
|
-
const sessionId = req.
|
|
109
|
+
router.get('/:id/command-buttons/runs', requireRootSessionAndProject, (req, res) => {
|
|
110
|
+
const sessionId = req.rootSessionId;
|
|
103
111
|
|
|
104
112
|
const activeRuns = commandRunner.getRunsBySession(sessionId);
|
|
105
113
|
res.json(activeRuns);
|
|
106
114
|
});
|
|
107
115
|
|
|
108
116
|
// GET /api/sessions/:id/command-buttons/runs/:runId - Get single run by ID
|
|
109
|
-
router.get('/:id/command-buttons/runs/:runId',
|
|
110
|
-
const {
|
|
117
|
+
router.get('/:id/command-buttons/runs/:runId', requireRootSessionAndProject, (req, res) => {
|
|
118
|
+
const { runId } = req.params;
|
|
119
|
+
const sessionId = req.rootSessionId;
|
|
111
120
|
|
|
112
121
|
// Check if run is currently running (in memory)
|
|
113
122
|
if (commandRunner.isRunning(runId)) {
|
|
@@ -136,8 +145,8 @@ router.get('/:id/command-buttons/runs/:runId', requireSession, (req, res) => {
|
|
|
136
145
|
});
|
|
137
146
|
|
|
138
147
|
// DELETE /api/sessions/:id/command-buttons/runs/:runId - Delete a command run record
|
|
139
|
-
router.delete('/:id/command-buttons/runs/:runId',
|
|
140
|
-
const sessionId = req.
|
|
148
|
+
router.delete('/:id/command-buttons/runs/:runId', requireRootSessionAndProject, (req, res) => {
|
|
149
|
+
const sessionId = req.rootSessionId;
|
|
141
150
|
const { runId } = req.params;
|
|
142
151
|
|
|
143
152
|
const run = commandRuns.getById(runId);
|
|
@@ -151,7 +160,7 @@ router.delete('/:id/command-buttons/runs/:runId', requireSessionAndProject, (req
|
|
|
151
160
|
|
|
152
161
|
commandRuns.deleteById(runId);
|
|
153
162
|
|
|
154
|
-
const projectId = req.
|
|
163
|
+
const projectId = req.rootSession_.projectId;
|
|
155
164
|
|
|
156
165
|
// Broadcast deletion to session and project subscribers
|
|
157
166
|
broadcastToSession(sessionId, WS_MESSAGE_TYPES.COMMAND_RUN_DELETED, {
|
|
@@ -170,18 +179,21 @@ router.delete('/:id/command-buttons/runs/:runId', requireSessionAndProject, (req
|
|
|
170
179
|
});
|
|
171
180
|
|
|
172
181
|
// DELETE /api/sessions/:id/command-buttons/:buttonId/runs/all - Delete all runs for a button in a session
|
|
173
|
-
router.delete('/:id/command-buttons/:buttonId/runs/all',
|
|
174
|
-
const sessionId = req.
|
|
182
|
+
router.delete('/:id/command-buttons/:buttonId/runs/all', requireRootSessionAndProject, (req, res) => {
|
|
183
|
+
const sessionId = req.rootSessionId;
|
|
175
184
|
const { buttonId } = req.params;
|
|
176
185
|
|
|
177
186
|
const button = commandButtons.getById(buttonId);
|
|
178
187
|
if (!button) {
|
|
179
188
|
return res.status(404).json({ error: 'Command button not found' });
|
|
180
189
|
}
|
|
190
|
+
if (button.projectId !== req.rootSession_.projectId) {
|
|
191
|
+
return res.status(404).json({ error: 'Command button not found' });
|
|
192
|
+
}
|
|
181
193
|
|
|
182
194
|
const { deletedRuns } = commandRuns.deleteByButtonAndSession(buttonId, sessionId);
|
|
183
195
|
|
|
184
|
-
const projectId = req.
|
|
196
|
+
const projectId = req.rootSession_.projectId;
|
|
185
197
|
|
|
186
198
|
// Broadcast individual COMMAND_RUN_DELETED events for each deleted run
|
|
187
199
|
for (const run of deletedRuns) {
|
|
@@ -202,12 +214,18 @@ router.delete('/:id/command-buttons/:buttonId/runs/all', requireSessionAndProjec
|
|
|
202
214
|
});
|
|
203
215
|
|
|
204
216
|
// POST /api/sessions/:id/command-buttons/runs/:runId/kill - Kill running command
|
|
205
|
-
router.post('/:id/command-buttons/runs/:runId/kill',
|
|
206
|
-
const sessionId = req.
|
|
217
|
+
router.post('/:id/command-buttons/runs/:runId/kill', requireRootSessionAndProject, (req, res) => {
|
|
218
|
+
const sessionId = req.rootSessionId;
|
|
207
219
|
const runId = req.params.runId;
|
|
208
220
|
|
|
209
221
|
console.log(`[KILL] Kill request for runId: ${runId}, sessionId: ${sessionId}`);
|
|
210
222
|
|
|
223
|
+
const activeRuns = commandRunner.getRunsBySession(sessionId);
|
|
224
|
+
const activeRun = activeRuns.find((run) => run.runId === runId);
|
|
225
|
+
if (!activeRun) {
|
|
226
|
+
return res.status(404).json({ error: 'Run not found or already completed' });
|
|
227
|
+
}
|
|
228
|
+
|
|
211
229
|
const killed = commandRunner.kill(runId);
|
|
212
230
|
console.log(`[KILL] Kill result: ${killed} for runId: ${runId}`);
|
|
213
231
|
if (!killed) {
|
|
@@ -57,6 +57,7 @@ router.post('/:id/start', requireSession, async (req, res) => {
|
|
|
57
57
|
const updatedSession = await startDraft(req.session_, {
|
|
58
58
|
prompt: req.body.prompt,
|
|
59
59
|
model: req.body.model,
|
|
60
|
+
providerId: req.body.providerId,
|
|
60
61
|
});
|
|
61
62
|
|
|
62
63
|
res.json({ success: true, session: updatedSession });
|
|
@@ -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 });
|