specrails-desktop 2.2.0 → 2.3.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/client/dist/assets/ActivityFeedPage-3Veccrvk.js +1 -0
- package/client/dist/assets/AgentsPage-2mFPghP4.js +86 -0
- package/client/dist/assets/{AnalyticsPage-D6LE6wG2.js → AnalyticsPage-Dyyz1ht3.js} +1 -1
- package/client/dist/assets/{BarChart-B366kDEj.js → BarChart-CMdLa6Es.js} +2 -2
- package/client/dist/assets/CodePage-D7Xwjhut.js +2 -0
- package/client/dist/assets/{DesktopAnalyticsPage-DG5LA_WO.js → DesktopAnalyticsPage-CTNwb639.js} +1 -1
- package/client/dist/assets/{DocsDialog-ChQ1oXLC.js → DocsDialog-D8yoyZDD.js} +2 -2
- package/client/dist/assets/{DocsPage-BfGH8NUf.js → DocsPage-CeO-fAxy.js} +2 -2
- package/client/dist/assets/{ExportDropdown-9tRrlfM7.js → ExportDropdown-DuoZcdYN.js} +1 -1
- package/client/dist/assets/{IntegrationsPage-DANIzihd.js → IntegrationsPage-iIZ0UEzf.js} +3 -3
- package/client/dist/assets/JobDetailPage-DgJHAH2m.js +16 -0
- package/client/dist/assets/JobsPage-Bv_RpRAE.js +1 -0
- package/client/dist/assets/code-BtsmPQLV.js +1 -0
- package/client/dist/assets/code-CY85RXZU.js +1 -0
- package/client/dist/assets/code-Coa8f2Sh.js +1 -0
- package/client/dist/assets/code-D1z-YDt-.js +1 -0
- package/client/dist/assets/code-DDU0CRS0.js +1 -0
- package/client/dist/assets/code-L35Loak_.js +1 -0
- package/client/dist/assets/code-g0qFMzyg.js +1 -0
- package/client/dist/assets/code-zCwBt3Uu.js +1 -0
- package/client/dist/assets/{dist-js-BvQ52Q67.js → dist-js-4UEGaKhD.js} +1 -1
- package/client/dist/assets/{dist-js-XEilFTNz.js → dist-js-H6hyhSuv.js} +1 -1
- package/client/dist/assets/{index-CNiaj7Sj.js → index-CGHKpC-N.js} +13 -13
- package/client/dist/assets/index-D17R4Cjc.css +2 -0
- package/client/dist/assets/{lib-DZJmnErt.js → lib-Cs5FrUJI.js} +1 -1
- package/client/dist/assets/{useProjectCache-H0T8Ot9j.js → useProjectCache-BZWYV-w-.js} +1 -1
- package/client/dist/index.html +3 -3
- package/package.json +1 -1
- package/server/dist/agent-refine-manager.js +128 -153
- package/server/dist/chat-manager.js +246 -0
- package/server/dist/code-explorer-router.js +78 -0
- package/server/dist/command-resolver.js +17 -0
- package/server/dist/contract-refine-runner.js +42 -10
- package/server/dist/db.js +6 -0
- package/server/dist/desktop-db.js +3 -0
- package/server/dist/explore-stdin-session.js +129 -0
- package/server/dist/mobile/mobile-auth.js +16 -0
- package/server/dist/project-router-chat.js +218 -0
- package/server/dist/project-router-helpers.js +275 -0
- package/server/dist/project-router-jobs.js +389 -0
- package/server/dist/project-router-settings.js +312 -0
- package/server/dist/project-router-setup.js +456 -0
- package/server/dist/project-router-spending.js +320 -0
- package/server/dist/project-router-terminals.js +312 -0
- package/server/dist/project-router-tickets.js +1767 -0
- package/server/dist/project-router.js +27 -3943
- package/server/dist/providers/claude-adapter.js +58 -17
- package/server/dist/providers/codex-adapter.js +6 -0
- package/server/dist/spawn-lifecycle.js +117 -0
- package/client/dist/assets/ActivityFeedPage-BupGdGjj.js +0 -1
- package/client/dist/assets/AgentsPage-F3xksiLd.js +0 -86
- package/client/dist/assets/CodePage-DLwCJgQ0.js +0 -2
- package/client/dist/assets/JobDetailPage-1RtejIOB.js +0 -16
- package/client/dist/assets/JobsPage-NuDf5Zbx.js +0 -1
- package/client/dist/assets/code-AL1rVIMb.js +0 -1
- package/client/dist/assets/code-C0BKpkht.js +0 -1
- package/client/dist/assets/code-C0FTS3ew.js +0 -1
- package/client/dist/assets/code-CPcHxzxw.js +0 -1
- package/client/dist/assets/code-D3ryDniw.js +0 -1
- package/client/dist/assets/code-D3zVVQTj.js +0 -1
- package/client/dist/assets/code-PCmfS3dn.js +0 -1
- package/client/dist/assets/code-exI0G5Wd.js +0 -1
- package/client/dist/assets/index-DgFfrrTX.css +0 -2
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerSettingsRoutes = registerSettingsRoutes;
|
|
7
|
+
// Domain routes extracted from project-router.ts (settings).
|
|
8
|
+
// Registered on the shared router by createProjectRouter — behaviour-preserving.
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const db_1 = require("./db");
|
|
12
|
+
const telemetry_export_1 = require("./telemetry-export");
|
|
13
|
+
const context_budget_1 = require("./context-budget");
|
|
14
|
+
const context_scope_1 = require("./context-scope");
|
|
15
|
+
const terminal_settings_1 = require("./terminal-settings");
|
|
16
|
+
const terminal_marks_store_1 = require("./terminal-marks-store");
|
|
17
|
+
const project_router_helpers_1 = require("./project-router-helpers");
|
|
18
|
+
function registerSettingsRoutes(deps) {
|
|
19
|
+
const { router, registry, ctx, ticketPath } = deps;
|
|
20
|
+
// ─── Project settings (pipeline telemetry) ───────────────────────────────────
|
|
21
|
+
router.get('/:projectId/settings', (req, res) => {
|
|
22
|
+
const settings = (0, db_1.getProjectSettings)(ctx(req).db);
|
|
23
|
+
res.json(settings);
|
|
24
|
+
});
|
|
25
|
+
// ─── Per-project Quick mode Contract Refine last-used value ─────────────────
|
|
26
|
+
router.get('/:projectId/add-spec-quick-contract-refine-last', (req, res) => {
|
|
27
|
+
res.json({
|
|
28
|
+
enabled: (0, db_1.getQuickContractRefineLast)(ctx(req).db),
|
|
29
|
+
configured: (0, db_1.hasQuickContractRefineLast)(ctx(req).db),
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
router.patch('/:projectId/add-spec-quick-contract-refine-last', (req, res) => {
|
|
33
|
+
const enabled = req.body?.enabled;
|
|
34
|
+
if (typeof enabled !== 'boolean') {
|
|
35
|
+
res.status(400).json({ error: 'enabled must be a boolean' });
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
(0, db_1.setQuickContractRefineLast)(ctx(req).db, enabled);
|
|
39
|
+
res.json({ enabled: (0, db_1.getQuickContractRefineLast)(ctx(req).db) });
|
|
40
|
+
});
|
|
41
|
+
// ─── Add Spec context scope ────────────────────────────────────────────────
|
|
42
|
+
router.get('/:projectId/context-budget', (req, res) => {
|
|
43
|
+
const { project } = ctx(req);
|
|
44
|
+
try {
|
|
45
|
+
const budget = (0, context_budget_1.getContextBudget)(project.id, project.path);
|
|
46
|
+
res.json(budget);
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
console.error('[project-router] context-budget failed:', err);
|
|
50
|
+
res.status(500).json({ error: 'failed to compute context budget' });
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
router.get('/:projectId/context-scope-last', (req, res) => {
|
|
54
|
+
const scope = (0, context_scope_1.getLastContextScope)(ctx(req).db, 'explore');
|
|
55
|
+
res.json({ scope });
|
|
56
|
+
});
|
|
57
|
+
router.patch('/:projectId/context-scope-last', (req, res) => {
|
|
58
|
+
const body = req.body;
|
|
59
|
+
if (!body || typeof body !== 'object') {
|
|
60
|
+
res.status(400).json({ error: 'body must be an object' });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Validate booleans-only for any provided key.
|
|
64
|
+
for (const key of ['specrails', 'openspec', 'full', 'mcp', 'contractRefine', 'userMcp']) {
|
|
65
|
+
if (body[key] !== undefined && typeof body[key] !== 'boolean') {
|
|
66
|
+
res.status(400).json({ error: `${key} must be a boolean` });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const current = (0, context_scope_1.getLastContextScope)(ctx(req).db, 'explore');
|
|
71
|
+
const merged = (0, context_scope_1.normalizeContextScope)({ ...current, ...body }, current);
|
|
72
|
+
(0, context_scope_1.setLastContextScope)(ctx(req).db, merged);
|
|
73
|
+
res.json({ scope: merged });
|
|
74
|
+
});
|
|
75
|
+
router.patch('/:projectId/settings', (req, res) => {
|
|
76
|
+
const { pipelineTelemetryEnabled, orchestratorModel, prePrompt, ultraPrePrompt } = req.body ?? {};
|
|
77
|
+
const patch = {};
|
|
78
|
+
if (pipelineTelemetryEnabled !== undefined) {
|
|
79
|
+
patch.pipelineTelemetryEnabled = Boolean(pipelineTelemetryEnabled);
|
|
80
|
+
}
|
|
81
|
+
const VALID_MODELS = ['sonnet', 'opus', 'haiku'];
|
|
82
|
+
if (orchestratorModel !== undefined) {
|
|
83
|
+
if (typeof orchestratorModel !== 'string' || !VALID_MODELS.includes(orchestratorModel)) {
|
|
84
|
+
res.status(400).json({ error: `orchestratorModel must be one of: ${VALID_MODELS.join(', ')}` });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
patch.orchestratorModel = orchestratorModel;
|
|
88
|
+
}
|
|
89
|
+
if (prePrompt !== undefined) {
|
|
90
|
+
if (typeof prePrompt !== 'string') {
|
|
91
|
+
res.status(400).json({ error: 'prePrompt must be a string' });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
patch.prePrompt = prePrompt;
|
|
95
|
+
}
|
|
96
|
+
if (ultraPrePrompt !== undefined) {
|
|
97
|
+
if (typeof ultraPrePrompt !== 'string') {
|
|
98
|
+
res.status(400).json({ error: 'ultraPrePrompt must be a string' });
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
patch.ultraPrePrompt = ultraPrePrompt;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
(0, db_1.updateProjectSettings)(ctx(req).db, patch);
|
|
105
|
+
res.json({ ok: true, settings: (0, db_1.getProjectSettings)(ctx(req).db) });
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
console.error('[project-router] settings patch error:', err);
|
|
109
|
+
res.status(500).json({ error: 'Failed to update settings' });
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
// ─── Agent models ────────────────────────────────────────────────────────────
|
|
113
|
+
router.get('/:projectId/agent-models', (req, res) => {
|
|
114
|
+
const { project } = ctx(req);
|
|
115
|
+
const agents = (0, project_router_helpers_1.readAgentModels)(project.path);
|
|
116
|
+
res.json({ agents });
|
|
117
|
+
});
|
|
118
|
+
router.patch('/:projectId/agent-models', (req, res) => {
|
|
119
|
+
const { project } = ctx(req);
|
|
120
|
+
const { defaultModel, overrides } = req.body ?? {};
|
|
121
|
+
// Validate defaultModel if provided
|
|
122
|
+
if (defaultModel !== undefined) {
|
|
123
|
+
if (typeof defaultModel !== 'string' || !project_router_helpers_1.VALID_MODEL_ALIASES.includes(defaultModel)) {
|
|
124
|
+
res.status(400).json({ error: `Invalid model alias. Must be one of: ${project_router_helpers_1.VALID_MODEL_ALIASES.join(', ')}` });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Validate overrides map if provided
|
|
129
|
+
if (overrides !== undefined) {
|
|
130
|
+
if (typeof overrides !== 'object' || Array.isArray(overrides) || overrides === null) {
|
|
131
|
+
res.status(400).json({ error: 'overrides must be an object' });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
for (const [agentName, modelValue] of Object.entries(overrides)) {
|
|
135
|
+
if (typeof modelValue !== 'string' || !project_router_helpers_1.VALID_MODEL_ALIASES.includes(modelValue)) {
|
|
136
|
+
res.status(400).json({ error: `Invalid model alias for agent "${agentName}". Must be one of: ${project_router_helpers_1.VALID_MODEL_ALIASES.join(', ')}` });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const configDir = path_1.default.join(project.path, '.specrails');
|
|
142
|
+
const configPath = path_1.default.join(configDir, 'install-config.yaml');
|
|
143
|
+
// Read existing config or build default shape
|
|
144
|
+
let existingConfig = {
|
|
145
|
+
version: 1,
|
|
146
|
+
provider: 'claude',
|
|
147
|
+
tier: 'quick',
|
|
148
|
+
agents: { selected: [], excluded: [] },
|
|
149
|
+
models: { preset: 'balanced', defaults: { model: 'sonnet' }, overrides: {} },
|
|
150
|
+
agent_teams: false,
|
|
151
|
+
};
|
|
152
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
153
|
+
try {
|
|
154
|
+
const text = fs_1.default.readFileSync(configPath, 'utf-8');
|
|
155
|
+
// Parse fields we care about from the existing config text
|
|
156
|
+
const versionMatch = text.match(/^version:\s*(\d+)/m);
|
|
157
|
+
const providerMatch = text.match(/^provider:\s*(\S+)/m);
|
|
158
|
+
const tierMatch = text.match(/^tier:\s*(\S+)/m);
|
|
159
|
+
const presetMatch = text.match(/preset:\s*(\S+)/);
|
|
160
|
+
const agentTeamsMatch = text.match(/^agent_teams:\s*(\S+)/m);
|
|
161
|
+
// Parse selected agents list
|
|
162
|
+
const selectedMatch = text.match(/selected:\s*\[([^\]]*)\]/);
|
|
163
|
+
const excludedMatch = text.match(/excluded:\s*\[([^\]]*)\]/);
|
|
164
|
+
const parsedSelected = selectedMatch
|
|
165
|
+
? selectedMatch[1].split(',').map(s => s.trim()).filter(Boolean)
|
|
166
|
+
: [];
|
|
167
|
+
const parsedExcluded = excludedMatch
|
|
168
|
+
? excludedMatch[1].split(',').map(s => s.trim()).filter(Boolean)
|
|
169
|
+
: [];
|
|
170
|
+
// Parse existing overrides to merge
|
|
171
|
+
const existingOverrides = {};
|
|
172
|
+
const overridesBlockMatch = text.match(/overrides:([\s\S]*?)(?:\n\S|$)/);
|
|
173
|
+
if (overridesBlockMatch) {
|
|
174
|
+
const block = overridesBlockMatch[1];
|
|
175
|
+
const overrideLines = block.match(/^ {2,}(\S+):\s*(\S+)/gm) ?? [];
|
|
176
|
+
for (const line of overrideLines) {
|
|
177
|
+
const m = line.match(/^\s+(\S+):\s*(\S+)/);
|
|
178
|
+
if (m)
|
|
179
|
+
existingOverrides[m[1]] = m[2];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
existingConfig = {
|
|
183
|
+
version: versionMatch ? parseInt(versionMatch[1], 10) : 1,
|
|
184
|
+
provider: providerMatch ? providerMatch[1] : 'claude',
|
|
185
|
+
tier: tierMatch ? tierMatch[1] : 'quick',
|
|
186
|
+
agents: { selected: parsedSelected, excluded: parsedExcluded },
|
|
187
|
+
models: {
|
|
188
|
+
preset: presetMatch ? presetMatch[1] : 'balanced',
|
|
189
|
+
defaults: { model: 'sonnet' },
|
|
190
|
+
overrides: existingOverrides,
|
|
191
|
+
},
|
|
192
|
+
agent_teams: agentTeamsMatch ? agentTeamsMatch[1] === 'true' : false,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
// use defaults
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Merge new values into config
|
|
200
|
+
const mergedModels = existingConfig.models;
|
|
201
|
+
if (defaultModel !== undefined) {
|
|
202
|
+
mergedModels.defaults = { model: defaultModel };
|
|
203
|
+
}
|
|
204
|
+
if (overrides !== undefined) {
|
|
205
|
+
mergedModels.overrides = overrides;
|
|
206
|
+
}
|
|
207
|
+
existingConfig.models = mergedModels;
|
|
208
|
+
try {
|
|
209
|
+
fs_1.default.mkdirSync(configDir, { recursive: true });
|
|
210
|
+
const yaml = (0, project_router_helpers_1.serializeInstallConfigYaml)(existingConfig);
|
|
211
|
+
fs_1.default.writeFileSync(configPath, yaml, 'utf-8');
|
|
212
|
+
(0, project_router_helpers_1.applyModelConfig)(project.path);
|
|
213
|
+
const agents = (0, project_router_helpers_1.readAgentModels)(project.path);
|
|
214
|
+
res.json({ agents });
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
console.error('[project-router] agent-models patch error:', err);
|
|
218
|
+
res.status(500).json({ error: `Failed to apply model config: ${err}` });
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
// ─── Diagnostic export ───────────────────────────────────────────────────────
|
|
222
|
+
router.get('/:projectId/jobs/:jobId/diagnostic', async (req, res) => {
|
|
223
|
+
const { db } = ctx(req);
|
|
224
|
+
const jobId = req.params.jobId;
|
|
225
|
+
const blob = (0, db_1.getTelemetryBlob)(db, jobId);
|
|
226
|
+
if (!blob) {
|
|
227
|
+
res.status(404).json({ error: 'No telemetry data for this job' });
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (blob.state === 'expired') {
|
|
231
|
+
res.status(410).json({ error: 'Telemetry data has been expired and is no longer available' });
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const job = (0, db_1.getJob)(db, jobId);
|
|
235
|
+
if (!job) {
|
|
236
|
+
res.status(404).json({ error: 'Job not found' });
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const summaries = (0, db_1.getTelemetrySummaries)(db, jobId);
|
|
240
|
+
const events = (0, db_1.getJobEvents)(db, jobId);
|
|
241
|
+
try {
|
|
242
|
+
const dateStr = new Date().toISOString().slice(0, 10);
|
|
243
|
+
const filename = `specrails-diagnostic-${jobId}-${dateStr}.zip`;
|
|
244
|
+
res.setHeader('Content-Type', 'application/zip');
|
|
245
|
+
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
|
|
246
|
+
const profileRow = db
|
|
247
|
+
.prepare(`SELECT profile_name, profile_json FROM job_profiles WHERE job_id = ?`)
|
|
248
|
+
.get(jobId);
|
|
249
|
+
const { homeJobSnapshotPath } = require('./plugins/paths');
|
|
250
|
+
const pluginSnap = homeJobSnapshotPath(req.projectCtx.project.slug, jobId);
|
|
251
|
+
await (0, telemetry_export_1.createDiagnosticZip)(res, {
|
|
252
|
+
job,
|
|
253
|
+
blob,
|
|
254
|
+
summaries,
|
|
255
|
+
events,
|
|
256
|
+
profile: profileRow ? { name: profileRow.profile_name, json: profileRow.profile_json } : null,
|
|
257
|
+
pluginSnapshotPath: pluginSnap,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
if (!res.headersSent) {
|
|
262
|
+
console.error('[project-router] diagnostic export error:', err);
|
|
263
|
+
res.status(500).json({ error: 'Failed to create diagnostic export' });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
// ─── Terminal command marks ────────────────────────────────────────────────
|
|
268
|
+
// GET /api/projects/:projectId/terminals/:id/marks?limit=&before=
|
|
269
|
+
router.get('/:projectId/terminals/:id/marks', (req, res) => {
|
|
270
|
+
const projectCtx = ctx(req);
|
|
271
|
+
const sessionId = req.params.id;
|
|
272
|
+
const limit = parseInt(req.query.limit ?? '100', 10);
|
|
273
|
+
const before = req.query.before ? parseInt(req.query.before, 10) : undefined;
|
|
274
|
+
const marks = (0, terminal_marks_store_1.listMarks)(projectCtx.db, sessionId, {
|
|
275
|
+
limit: Number.isFinite(limit) ? limit : 100,
|
|
276
|
+
before: typeof before === 'number' && Number.isFinite(before) ? before : undefined,
|
|
277
|
+
});
|
|
278
|
+
res.json({ marks });
|
|
279
|
+
});
|
|
280
|
+
// ─── Terminal settings (per-project override layer) ────────────────────────
|
|
281
|
+
// GET /api/projects/:projectId/terminal-settings — returns { resolved, override, desktopDefaults }
|
|
282
|
+
router.get('/:projectId/terminal-settings', (req, res) => {
|
|
283
|
+
const projectCtx = ctx(req);
|
|
284
|
+
const desktopDefaults = (0, terminal_settings_1.getDesktopTerminalSettings)(registry.desktopDb);
|
|
285
|
+
const override = (0, terminal_settings_1.getProjectOverride)(projectCtx.db);
|
|
286
|
+
const resolved = (0, terminal_settings_1.resolveTerminalSettings)(registry.desktopDb, projectCtx.db);
|
|
287
|
+
res.json({ resolved, override, desktopDefaults });
|
|
288
|
+
});
|
|
289
|
+
// PATCH /api/projects/:projectId/terminal-settings — partial update of override
|
|
290
|
+
// (null value for a field clears that override)
|
|
291
|
+
router.patch('/:projectId/terminal-settings', (req, res) => {
|
|
292
|
+
const projectCtx = ctx(req);
|
|
293
|
+
if (!req.body || typeof req.body !== 'object' || Array.isArray(req.body)) {
|
|
294
|
+
res.status(400).json({ error: 'invalid body' });
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
(0, terminal_settings_1.patchProjectOverride)(projectCtx.db, req.body);
|
|
299
|
+
const desktopDefaults = (0, terminal_settings_1.getDesktopTerminalSettings)(registry.desktopDb);
|
|
300
|
+
const override = (0, terminal_settings_1.getProjectOverride)(projectCtx.db);
|
|
301
|
+
const resolved = (0, terminal_settings_1.resolveTerminalSettings)(registry.desktopDb, projectCtx.db);
|
|
302
|
+
res.json({ resolved, override, desktopDefaults });
|
|
303
|
+
}
|
|
304
|
+
catch (err) {
|
|
305
|
+
if (err instanceof terminal_settings_1.TerminalSettingsValidationError) {
|
|
306
|
+
res.status(400).json({ error: 'validation_failed', field: err.field, message: err.message });
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
throw err;
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
}
|