@ottocode/server 0.1.264 → 0.1.266
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 +3 -3
- package/src/routes/auth/copilot.ts +699 -0
- package/src/routes/auth/oauth.ts +578 -0
- package/src/routes/auth/onboarding.ts +45 -0
- package/src/routes/auth/providers.ts +189 -0
- package/src/routes/auth/service.ts +167 -0
- package/src/routes/auth/state.ts +23 -0
- package/src/routes/auth/status.ts +203 -0
- package/src/routes/auth/wallet.ts +229 -0
- package/src/routes/auth.ts +12 -2080
- package/src/routes/config/models-service.ts +411 -0
- package/src/routes/config/models.ts +6 -426
- package/src/routes/config/providers-service.ts +237 -0
- package/src/routes/config/providers.ts +10 -242
- package/src/routes/files/handlers.ts +297 -0
- package/src/routes/files/service.ts +313 -0
- package/src/routes/files.ts +12 -608
- package/src/routes/git/commit-service.ts +207 -0
- package/src/routes/git/commit.ts +6 -220
- package/src/routes/git/remote-service.ts +116 -0
- package/src/routes/git/remote.ts +8 -115
- package/src/routes/git/staging-service.ts +111 -0
- package/src/routes/git/staging.ts +10 -205
- package/src/routes/mcp/auth.ts +338 -0
- package/src/routes/mcp/lifecycle.ts +263 -0
- package/src/routes/mcp/servers.ts +212 -0
- package/src/routes/mcp/service.ts +664 -0
- package/src/routes/mcp/state.ts +13 -0
- package/src/routes/mcp.ts +6 -1233
- package/src/routes/ottorouter/billing.ts +593 -0
- package/src/routes/ottorouter/service.ts +92 -0
- package/src/routes/ottorouter/topup.ts +301 -0
- package/src/routes/ottorouter/wallet.ts +370 -0
- package/src/routes/ottorouter.ts +6 -1319
- package/src/routes/research/service.ts +339 -0
- package/src/routes/research.ts +12 -390
- package/src/routes/sessions/crud.ts +563 -0
- package/src/routes/sessions/queue.ts +242 -0
- package/src/routes/sessions/retry.ts +121 -0
- package/src/routes/sessions/service.ts +768 -0
- package/src/routes/sessions/share.ts +434 -0
- package/src/routes/sessions.ts +8 -1977
- package/src/routes/skills/service.ts +221 -0
- package/src/routes/skills/spec.ts +309 -0
- package/src/routes/skills.ts +31 -909
- package/src/routes/terminals/service.ts +326 -0
- package/src/routes/terminals.ts +19 -295
- package/src/routes/tunnel/service.ts +217 -0
- package/src/routes/tunnel.ts +29 -219
- package/src/runtime/agent/registry-prompts.ts +147 -0
- package/src/runtime/agent/registry.ts +6 -124
- package/src/runtime/agent/runner-errors.ts +116 -0
- package/src/runtime/agent/runner-reminders.ts +45 -0
- package/src/runtime/agent/runner-setup-model.ts +75 -0
- package/src/runtime/agent/runner-setup-prompt.ts +185 -0
- package/src/runtime/agent/runner-setup-tools.ts +103 -0
- package/src/runtime/agent/runner-setup-utils.ts +21 -0
- package/src/runtime/agent/runner-setup.ts +54 -288
- package/src/runtime/agent/runner-telemetry.ts +112 -0
- package/src/runtime/agent/runner-text.ts +108 -0
- package/src/runtime/agent/runner-tool-observer.ts +86 -0
- package/src/runtime/agent/runner.ts +79 -378
- package/src/runtime/ask/service.ts +1 -0
- package/src/runtime/provider/custom.ts +73 -0
- package/src/runtime/provider/index.ts +6 -85
- package/src/runtime/provider/reasoning-builders.ts +280 -0
- package/src/runtime/provider/reasoning.ts +68 -264
- package/src/runtime/provider/xai.ts +8 -0
- package/src/tools/adapter/events.ts +116 -0
- package/src/tools/adapter/execution.ts +160 -0
- package/src/tools/adapter/pending.ts +37 -0
- package/src/tools/adapter/persistence.ts +166 -0
- package/src/tools/adapter/results.ts +97 -0
- package/src/tools/adapter.ts +124 -451
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
import { sessions } from '@ottocode/database/schema';
|
|
2
|
+
import { hasConfiguredProvider, logger, type ProviderId } from '@ottocode/sdk';
|
|
3
|
+
import { and, desc, eq, ne } from 'drizzle-orm';
|
|
4
|
+
import type { Hono } from 'hono';
|
|
5
|
+
import { openApiRoute } from '../../openapi/route.ts';
|
|
6
|
+
import { serializeError } from '../../runtime/errors/api-error.ts';
|
|
7
|
+
import { resolveAgentConfig } from '../../runtime/agent/registry.ts';
|
|
8
|
+
import { createSession as createSessionRow } from '../../runtime/session/manager.ts';
|
|
9
|
+
import {
|
|
10
|
+
buildSessionPreferenceUpdates,
|
|
11
|
+
deleteSessionMessagesAndParts,
|
|
12
|
+
findSessionById,
|
|
13
|
+
loadProjectDb,
|
|
14
|
+
normalizeSessionRow,
|
|
15
|
+
} from './service.ts';
|
|
16
|
+
|
|
17
|
+
export function registerSessionCrudRoutes(app: Hono) {
|
|
18
|
+
// List sessions
|
|
19
|
+
openApiRoute(
|
|
20
|
+
app,
|
|
21
|
+
{
|
|
22
|
+
method: 'get',
|
|
23
|
+
path: '/v1/sessions',
|
|
24
|
+
tags: ['sessions'],
|
|
25
|
+
operationId: 'listSessions',
|
|
26
|
+
summary: 'List sessions',
|
|
27
|
+
parameters: [
|
|
28
|
+
{
|
|
29
|
+
in: 'query',
|
|
30
|
+
name: 'project',
|
|
31
|
+
required: false,
|
|
32
|
+
schema: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
},
|
|
35
|
+
description:
|
|
36
|
+
'Project root override (defaults to current working directory).',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
in: 'query',
|
|
40
|
+
name: 'limit',
|
|
41
|
+
schema: {
|
|
42
|
+
type: 'integer',
|
|
43
|
+
default: 50,
|
|
44
|
+
minimum: 1,
|
|
45
|
+
maximum: 200,
|
|
46
|
+
},
|
|
47
|
+
description: 'Maximum number of sessions to return',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
in: 'query',
|
|
51
|
+
name: 'offset',
|
|
52
|
+
schema: {
|
|
53
|
+
type: 'integer',
|
|
54
|
+
default: 0,
|
|
55
|
+
minimum: 0,
|
|
56
|
+
},
|
|
57
|
+
description: 'Offset for pagination',
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
responses: {
|
|
61
|
+
'200': {
|
|
62
|
+
description: 'OK',
|
|
63
|
+
content: {
|
|
64
|
+
'application/json': {
|
|
65
|
+
schema: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: {
|
|
68
|
+
items: {
|
|
69
|
+
type: 'array',
|
|
70
|
+
items: {
|
|
71
|
+
$ref: '#/components/schemas/Session',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
hasMore: {
|
|
75
|
+
type: 'boolean',
|
|
76
|
+
},
|
|
77
|
+
nextOffset: {
|
|
78
|
+
type: 'integer',
|
|
79
|
+
nullable: true,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
required: ['items', 'hasMore', 'nextOffset'],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
async (c) => {
|
|
90
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
91
|
+
const limit = Math.min(
|
|
92
|
+
Math.max(parseInt(c.req.query('limit') || '50', 10) || 50, 1),
|
|
93
|
+
200,
|
|
94
|
+
);
|
|
95
|
+
const offset = Math.max(
|
|
96
|
+
parseInt(c.req.query('offset') || '0', 10) || 0,
|
|
97
|
+
0,
|
|
98
|
+
);
|
|
99
|
+
const { cfg, db } = await loadProjectDb(projectRoot);
|
|
100
|
+
// Only return sessions for this project, excluding research sessions
|
|
101
|
+
const rows = await db
|
|
102
|
+
.select()
|
|
103
|
+
.from(sessions)
|
|
104
|
+
.where(
|
|
105
|
+
and(
|
|
106
|
+
eq(sessions.projectPath, cfg.projectRoot),
|
|
107
|
+
ne(sessions.sessionType, 'research'),
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
.orderBy(desc(sessions.lastActiveAt), desc(sessions.createdAt))
|
|
111
|
+
.limit(limit + 1)
|
|
112
|
+
.offset(offset);
|
|
113
|
+
const hasMore = rows.length > limit;
|
|
114
|
+
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
115
|
+
const normalized = page.map((r) =>
|
|
116
|
+
normalizeSessionRow(r, { includeRunning: true }),
|
|
117
|
+
);
|
|
118
|
+
return c.json({
|
|
119
|
+
items: normalized,
|
|
120
|
+
hasMore,
|
|
121
|
+
nextOffset: hasMore ? offset + limit : null,
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Create session
|
|
127
|
+
openApiRoute(
|
|
128
|
+
app,
|
|
129
|
+
{
|
|
130
|
+
method: 'post',
|
|
131
|
+
path: '/v1/sessions',
|
|
132
|
+
tags: ['sessions'],
|
|
133
|
+
operationId: 'createSession',
|
|
134
|
+
summary: 'Create a new session',
|
|
135
|
+
parameters: [
|
|
136
|
+
{
|
|
137
|
+
in: 'query',
|
|
138
|
+
name: 'project',
|
|
139
|
+
required: false,
|
|
140
|
+
schema: {
|
|
141
|
+
type: 'string',
|
|
142
|
+
},
|
|
143
|
+
description:
|
|
144
|
+
'Project root override (defaults to current working directory).',
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
requestBody: {
|
|
148
|
+
required: false,
|
|
149
|
+
content: {
|
|
150
|
+
'application/json': {
|
|
151
|
+
schema: {
|
|
152
|
+
type: 'object',
|
|
153
|
+
properties: {
|
|
154
|
+
title: {
|
|
155
|
+
type: 'string',
|
|
156
|
+
nullable: true,
|
|
157
|
+
},
|
|
158
|
+
agent: {
|
|
159
|
+
type: 'string',
|
|
160
|
+
},
|
|
161
|
+
provider: {
|
|
162
|
+
$ref: '#/components/schemas/Provider',
|
|
163
|
+
},
|
|
164
|
+
model: {
|
|
165
|
+
type: 'string',
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
responses: {
|
|
173
|
+
'201': {
|
|
174
|
+
description: 'Created',
|
|
175
|
+
content: {
|
|
176
|
+
'application/json': {
|
|
177
|
+
schema: {
|
|
178
|
+
$ref: '#/components/schemas/Session',
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
'400': {
|
|
184
|
+
description: 'Bad Request',
|
|
185
|
+
content: {
|
|
186
|
+
'application/json': {
|
|
187
|
+
schema: {
|
|
188
|
+
type: 'object',
|
|
189
|
+
properties: {
|
|
190
|
+
error: {
|
|
191
|
+
type: 'string',
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
required: ['error'],
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
async (c) => {
|
|
202
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
203
|
+
const { cfg, db } = await loadProjectDb(projectRoot);
|
|
204
|
+
const body = (await c.req.json().catch(() => ({}))) as Record<
|
|
205
|
+
string,
|
|
206
|
+
unknown
|
|
207
|
+
>;
|
|
208
|
+
const agent = (body.agent as string | undefined) ?? cfg.defaults.agent;
|
|
209
|
+
const agentCfg = await resolveAgentConfig(cfg.projectRoot, agent);
|
|
210
|
+
const providerCandidate =
|
|
211
|
+
typeof body.provider === 'string' ? body.provider : undefined;
|
|
212
|
+
const provider: ProviderId = (() => {
|
|
213
|
+
if (providerCandidate && hasConfiguredProvider(cfg, providerCandidate))
|
|
214
|
+
return providerCandidate;
|
|
215
|
+
if (hasConfiguredProvider(cfg, agentCfg.provider))
|
|
216
|
+
return agentCfg.provider;
|
|
217
|
+
return cfg.defaults.provider;
|
|
218
|
+
})();
|
|
219
|
+
const modelCandidate =
|
|
220
|
+
typeof body.model === 'string' ? body.model.trim() : undefined;
|
|
221
|
+
const model = modelCandidate?.length
|
|
222
|
+
? modelCandidate
|
|
223
|
+
: (agentCfg.model ?? cfg.defaults.model);
|
|
224
|
+
try {
|
|
225
|
+
const row = await createSessionRow({
|
|
226
|
+
db,
|
|
227
|
+
cfg,
|
|
228
|
+
agent,
|
|
229
|
+
provider,
|
|
230
|
+
model,
|
|
231
|
+
title: (body.title as string | null | undefined) ?? null,
|
|
232
|
+
});
|
|
233
|
+
return c.json(row, 201);
|
|
234
|
+
} catch (err) {
|
|
235
|
+
logger.error('Failed to create session', err);
|
|
236
|
+
const errorResponse = serializeError(err);
|
|
237
|
+
return c.json(errorResponse, errorResponse.error.status || 400);
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
// Get single session
|
|
243
|
+
openApiRoute(
|
|
244
|
+
app,
|
|
245
|
+
{
|
|
246
|
+
method: 'get',
|
|
247
|
+
path: '/v1/sessions/{sessionId}',
|
|
248
|
+
tags: ['sessions'],
|
|
249
|
+
operationId: 'getSession',
|
|
250
|
+
summary: 'Get a single session by ID',
|
|
251
|
+
parameters: [
|
|
252
|
+
{
|
|
253
|
+
in: 'path',
|
|
254
|
+
name: 'sessionId',
|
|
255
|
+
required: true,
|
|
256
|
+
schema: {
|
|
257
|
+
type: 'string',
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
in: 'query',
|
|
262
|
+
name: 'project',
|
|
263
|
+
required: false,
|
|
264
|
+
schema: {
|
|
265
|
+
type: 'string',
|
|
266
|
+
},
|
|
267
|
+
description:
|
|
268
|
+
'Project root override (defaults to current working directory).',
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
responses: {
|
|
272
|
+
'200': {
|
|
273
|
+
description: 'OK',
|
|
274
|
+
content: {
|
|
275
|
+
'application/json': {
|
|
276
|
+
schema: {
|
|
277
|
+
$ref: '#/components/schemas/Session',
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
'404': {
|
|
283
|
+
description: 'Bad Request',
|
|
284
|
+
content: {
|
|
285
|
+
'application/json': {
|
|
286
|
+
schema: {
|
|
287
|
+
type: 'object',
|
|
288
|
+
properties: {
|
|
289
|
+
error: {
|
|
290
|
+
type: 'string',
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
required: ['error'],
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
async (c) => {
|
|
301
|
+
try {
|
|
302
|
+
const sessionId = c.req.param('sessionId');
|
|
303
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
304
|
+
const { db } = await loadProjectDb(projectRoot);
|
|
305
|
+
const session = await findSessionById(db, sessionId);
|
|
306
|
+
if (!session) {
|
|
307
|
+
return c.json(
|
|
308
|
+
{ error: { message: 'Session not found', status: 404 } },
|
|
309
|
+
404,
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
return c.json(normalizeSessionRow(session));
|
|
313
|
+
} catch (err) {
|
|
314
|
+
logger.error('Failed to get session', err);
|
|
315
|
+
const errorResponse = serializeError(err);
|
|
316
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
// Update session preferences
|
|
322
|
+
openApiRoute(
|
|
323
|
+
app,
|
|
324
|
+
{
|
|
325
|
+
method: 'patch',
|
|
326
|
+
path: '/v1/sessions/{sessionId}',
|
|
327
|
+
tags: ['sessions'],
|
|
328
|
+
operationId: 'updateSession',
|
|
329
|
+
summary: 'Update session preferences',
|
|
330
|
+
parameters: [
|
|
331
|
+
{
|
|
332
|
+
in: 'path',
|
|
333
|
+
name: 'sessionId',
|
|
334
|
+
required: true,
|
|
335
|
+
schema: {
|
|
336
|
+
type: 'string',
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
in: 'query',
|
|
341
|
+
name: 'project',
|
|
342
|
+
required: false,
|
|
343
|
+
schema: {
|
|
344
|
+
type: 'string',
|
|
345
|
+
},
|
|
346
|
+
description:
|
|
347
|
+
'Project root override (defaults to current working directory).',
|
|
348
|
+
},
|
|
349
|
+
],
|
|
350
|
+
requestBody: {
|
|
351
|
+
required: true,
|
|
352
|
+
content: {
|
|
353
|
+
'application/json': {
|
|
354
|
+
schema: {
|
|
355
|
+
type: 'object',
|
|
356
|
+
properties: {
|
|
357
|
+
title: {
|
|
358
|
+
type: 'string',
|
|
359
|
+
},
|
|
360
|
+
agent: {
|
|
361
|
+
type: 'string',
|
|
362
|
+
},
|
|
363
|
+
provider: {
|
|
364
|
+
$ref: '#/components/schemas/Provider',
|
|
365
|
+
},
|
|
366
|
+
model: {
|
|
367
|
+
type: 'string',
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
responses: {
|
|
375
|
+
'200': {
|
|
376
|
+
description: 'OK',
|
|
377
|
+
content: {
|
|
378
|
+
'application/json': {
|
|
379
|
+
schema: {
|
|
380
|
+
$ref: '#/components/schemas/Session',
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
'400': {
|
|
386
|
+
description: 'Bad Request',
|
|
387
|
+
content: {
|
|
388
|
+
'application/json': {
|
|
389
|
+
schema: {
|
|
390
|
+
type: 'object',
|
|
391
|
+
properties: {
|
|
392
|
+
error: {
|
|
393
|
+
type: 'string',
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
required: ['error'],
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
'404': {
|
|
402
|
+
description: 'Bad Request',
|
|
403
|
+
content: {
|
|
404
|
+
'application/json': {
|
|
405
|
+
schema: {
|
|
406
|
+
type: 'object',
|
|
407
|
+
properties: {
|
|
408
|
+
error: {
|
|
409
|
+
type: 'string',
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
required: ['error'],
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
async (c) => {
|
|
420
|
+
try {
|
|
421
|
+
const sessionId = c.req.param('sessionId');
|
|
422
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
423
|
+
const { cfg, db } = await loadProjectDb(projectRoot);
|
|
424
|
+
|
|
425
|
+
const body = (await c.req.json().catch(() => ({}))) as Record<
|
|
426
|
+
string,
|
|
427
|
+
unknown
|
|
428
|
+
>;
|
|
429
|
+
|
|
430
|
+
const existingSession = await findSessionById(db, sessionId);
|
|
431
|
+
|
|
432
|
+
if (!existingSession) {
|
|
433
|
+
return c.json({ error: 'Session not found' }, 404);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Verify session belongs to current project
|
|
437
|
+
if (existingSession.projectPath !== cfg.projectRoot) {
|
|
438
|
+
return c.json({ error: 'Session not found in this project' }, 404);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const updateResult = await buildSessionPreferenceUpdates(
|
|
442
|
+
cfg,
|
|
443
|
+
existingSession,
|
|
444
|
+
body,
|
|
445
|
+
);
|
|
446
|
+
if (!updateResult.ok) {
|
|
447
|
+
return c.json({ error: updateResult.error }, updateResult.status);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Perform update
|
|
451
|
+
await db
|
|
452
|
+
.update(sessions)
|
|
453
|
+
.set(updateResult.updates)
|
|
454
|
+
.where(eq(sessions.id, sessionId));
|
|
455
|
+
|
|
456
|
+
// Return updated session
|
|
457
|
+
const updatedRows = await db
|
|
458
|
+
.select()
|
|
459
|
+
.from(sessions)
|
|
460
|
+
.where(eq(sessions.id, sessionId))
|
|
461
|
+
.limit(1);
|
|
462
|
+
|
|
463
|
+
return c.json(updatedRows[0]);
|
|
464
|
+
} catch (err) {
|
|
465
|
+
logger.error('Failed to update session', err);
|
|
466
|
+
const errorResponse = serializeError(err);
|
|
467
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
// Delete session
|
|
473
|
+
openApiRoute(
|
|
474
|
+
app,
|
|
475
|
+
{
|
|
476
|
+
method: 'delete',
|
|
477
|
+
path: '/v1/sessions/{sessionId}',
|
|
478
|
+
tags: ['sessions'],
|
|
479
|
+
operationId: 'deleteSession',
|
|
480
|
+
summary: 'Delete a session',
|
|
481
|
+
parameters: [
|
|
482
|
+
{
|
|
483
|
+
in: 'path',
|
|
484
|
+
name: 'sessionId',
|
|
485
|
+
required: true,
|
|
486
|
+
schema: {
|
|
487
|
+
type: 'string',
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
in: 'query',
|
|
492
|
+
name: 'project',
|
|
493
|
+
required: false,
|
|
494
|
+
schema: {
|
|
495
|
+
type: 'string',
|
|
496
|
+
},
|
|
497
|
+
description:
|
|
498
|
+
'Project root override (defaults to current working directory).',
|
|
499
|
+
},
|
|
500
|
+
],
|
|
501
|
+
responses: {
|
|
502
|
+
'200': {
|
|
503
|
+
description: 'OK',
|
|
504
|
+
content: {
|
|
505
|
+
'application/json': {
|
|
506
|
+
schema: {
|
|
507
|
+
type: 'object',
|
|
508
|
+
properties: {
|
|
509
|
+
success: {
|
|
510
|
+
type: 'boolean',
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
required: ['success'],
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
'404': {
|
|
519
|
+
description: 'Bad Request',
|
|
520
|
+
content: {
|
|
521
|
+
'application/json': {
|
|
522
|
+
schema: {
|
|
523
|
+
type: 'object',
|
|
524
|
+
properties: {
|
|
525
|
+
error: {
|
|
526
|
+
type: 'string',
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
required: ['error'],
|
|
530
|
+
},
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
},
|
|
536
|
+
async (c) => {
|
|
537
|
+
try {
|
|
538
|
+
const sessionId = c.req.param('sessionId');
|
|
539
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
540
|
+
const { cfg, db } = await loadProjectDb(projectRoot);
|
|
541
|
+
|
|
542
|
+
const existingSession = await findSessionById(db, sessionId);
|
|
543
|
+
|
|
544
|
+
if (!existingSession) {
|
|
545
|
+
return c.json({ error: 'Session not found' }, 404);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (existingSession.projectPath !== cfg.projectRoot) {
|
|
549
|
+
return c.json({ error: 'Session not found in this project' }, 404);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
await deleteSessionMessagesAndParts(db, sessionId);
|
|
553
|
+
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
|
554
|
+
|
|
555
|
+
return c.json({ success: true });
|
|
556
|
+
} catch (err) {
|
|
557
|
+
logger.error('Failed to delete session', err);
|
|
558
|
+
const errorResponse = serializeError(err);
|
|
559
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
560
|
+
}
|
|
561
|
+
},
|
|
562
|
+
);
|
|
563
|
+
}
|