@helmisatria/mcp-chrome-bridge 1.0.30
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/README.md +183 -0
- package/dist/README.md +25 -0
- package/dist/agent/attachment-service.d.ts +83 -0
- package/dist/agent/attachment-service.js +370 -0
- package/dist/agent/attachment-service.js.map +1 -0
- package/dist/agent/ccr-detector.d.ts +59 -0
- package/dist/agent/ccr-detector.js +311 -0
- package/dist/agent/ccr-detector.js.map +1 -0
- package/dist/agent/chat-service.d.ts +50 -0
- package/dist/agent/chat-service.js +439 -0
- package/dist/agent/chat-service.js.map +1 -0
- package/dist/agent/db/client.d.ts +26 -0
- package/dist/agent/db/client.js +244 -0
- package/dist/agent/db/client.js.map +1 -0
- package/dist/agent/db/index.d.ts +5 -0
- package/dist/agent/db/index.js +22 -0
- package/dist/agent/db/index.js.map +1 -0
- package/dist/agent/db/schema.d.ts +711 -0
- package/dist/agent/db/schema.js +121 -0
- package/dist/agent/db/schema.js.map +1 -0
- package/dist/agent/directory-picker.d.ts +11 -0
- package/dist/agent/directory-picker.js +149 -0
- package/dist/agent/directory-picker.js.map +1 -0
- package/dist/agent/engines/claude.d.ts +79 -0
- package/dist/agent/engines/claude.js +1338 -0
- package/dist/agent/engines/claude.js.map +1 -0
- package/dist/agent/engines/codex.d.ts +48 -0
- package/dist/agent/engines/codex.js +822 -0
- package/dist/agent/engines/codex.js.map +1 -0
- package/dist/agent/engines/types.d.ts +133 -0
- package/dist/agent/engines/types.js +3 -0
- package/dist/agent/engines/types.js.map +1 -0
- package/dist/agent/message-service.d.ts +56 -0
- package/dist/agent/message-service.js +198 -0
- package/dist/agent/message-service.js.map +1 -0
- package/dist/agent/open-project.d.ts +25 -0
- package/dist/agent/open-project.js +469 -0
- package/dist/agent/open-project.js.map +1 -0
- package/dist/agent/project-service.d.ts +49 -0
- package/dist/agent/project-service.js +254 -0
- package/dist/agent/project-service.js.map +1 -0
- package/dist/agent/project-types.d.ts +27 -0
- package/dist/agent/project-types.js +3 -0
- package/dist/agent/project-types.js.map +1 -0
- package/dist/agent/session-service.d.ts +198 -0
- package/dist/agent/session-service.js +292 -0
- package/dist/agent/session-service.js.map +1 -0
- package/dist/agent/storage.d.ts +27 -0
- package/dist/agent/storage.js +73 -0
- package/dist/agent/storage.js.map +1 -0
- package/dist/agent/stream-manager.d.ts +42 -0
- package/dist/agent/stream-manager.js +243 -0
- package/dist/agent/stream-manager.js.map +1 -0
- package/dist/agent/tool-bridge.d.ts +44 -0
- package/dist/agent/tool-bridge.js +50 -0
- package/dist/agent/tool-bridge.js.map +1 -0
- package/dist/agent/types.d.ts +6 -0
- package/dist/agent/types.js +3 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +224 -0
- package/dist/cli.js.map +1 -0
- package/dist/constant/index.d.ts +60 -0
- package/dist/constant/index.js +80 -0
- package/dist/constant/index.js.map +1 -0
- package/dist/file-handler.d.ts +41 -0
- package/dist/file-handler.js +295 -0
- package/dist/file-handler.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/mcp-server-stdio.d.ts +72 -0
- package/dist/mcp/mcp-server-stdio.js +143 -0
- package/dist/mcp/mcp-server-stdio.js.map +1 -0
- package/dist/mcp/mcp-server.d.ts +36 -0
- package/dist/mcp/mcp-server.js +26 -0
- package/dist/mcp/mcp-server.js.map +1 -0
- package/dist/mcp/register-tools.d.ts +2 -0
- package/dist/mcp/register-tools.js +148 -0
- package/dist/mcp/register-tools.js.map +1 -0
- package/dist/mcp/stdio-config.json +3 -0
- package/dist/native-messaging-host.d.ts +42 -0
- package/dist/native-messaging-host.js +312 -0
- package/dist/native-messaging-host.js.map +1 -0
- package/dist/run_host.bat +194 -0
- package/dist/run_host.sh +264 -0
- package/dist/scripts/browser-config.d.ts +28 -0
- package/dist/scripts/browser-config.js +229 -0
- package/dist/scripts/browser-config.js.map +1 -0
- package/dist/scripts/build.d.ts +1 -0
- package/dist/scripts/build.js +126 -0
- package/dist/scripts/build.js.map +1 -0
- package/dist/scripts/constant.d.ts +4 -0
- package/dist/scripts/constant.js +8 -0
- package/dist/scripts/constant.js.map +1 -0
- package/dist/scripts/doctor.d.ts +70 -0
- package/dist/scripts/doctor.js +930 -0
- package/dist/scripts/doctor.js.map +1 -0
- package/dist/scripts/postinstall.d.ts +2 -0
- package/dist/scripts/postinstall.js +246 -0
- package/dist/scripts/postinstall.js.map +1 -0
- package/dist/scripts/register-dev.d.ts +1 -0
- package/dist/scripts/register-dev.js +5 -0
- package/dist/scripts/register-dev.js.map +1 -0
- package/dist/scripts/register.d.ts +2 -0
- package/dist/scripts/register.js +28 -0
- package/dist/scripts/register.js.map +1 -0
- package/dist/scripts/report.d.ts +96 -0
- package/dist/scripts/report.js +686 -0
- package/dist/scripts/report.js.map +1 -0
- package/dist/scripts/utils.d.ts +64 -0
- package/dist/scripts/utils.js +443 -0
- package/dist/scripts/utils.js.map +1 -0
- package/dist/server/index.d.ts +35 -0
- package/dist/server/index.js +312 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/routes/agent.d.ts +21 -0
- package/dist/server/routes/agent.js +971 -0
- package/dist/server/routes/agent.js.map +1 -0
- package/dist/server/routes/index.d.ts +4 -0
- package/dist/server/routes/index.js +9 -0
- package/dist/server/routes/index.js.map +1 -0
- package/dist/trace-analyzer.d.ts +14 -0
- package/dist/trace-analyzer.js +113 -0
- package/dist/trace-analyzer.js.map +1 -0
- package/dist/util/logger.d.ts +1 -0
- package/dist/util/logger.js +43 -0
- package/dist/util/logger.js.map +1 -0
- package/package.json +91 -0
|
@@ -0,0 +1,971 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerAgentRoutes = registerAgentRoutes;
|
|
4
|
+
const constant_1 = require("../../constant");
|
|
5
|
+
const project_service_1 = require("../../agent/project-service");
|
|
6
|
+
const message_service_1 = require("../../agent/message-service");
|
|
7
|
+
const session_service_1 = require("../../agent/session-service");
|
|
8
|
+
const project_service_2 = require("../../agent/project-service");
|
|
9
|
+
const storage_1 = require("../../agent/storage");
|
|
10
|
+
const directory_picker_1 = require("../../agent/directory-picker");
|
|
11
|
+
const attachment_service_1 = require("../../agent/attachment-service");
|
|
12
|
+
const open_project_1 = require("../../agent/open-project");
|
|
13
|
+
// Valid engine names for validation
|
|
14
|
+
const VALID_ENGINE_NAMES = ['claude', 'codex', 'cursor', 'qwen', 'glm'];
|
|
15
|
+
function isValidEngineName(name) {
|
|
16
|
+
return VALID_ENGINE_NAMES.includes(name);
|
|
17
|
+
}
|
|
18
|
+
// Valid open project targets
|
|
19
|
+
const VALID_OPEN_TARGETS = ['vscode', 'terminal'];
|
|
20
|
+
function isValidOpenTarget(target) {
|
|
21
|
+
return VALID_OPEN_TARGETS.includes(target);
|
|
22
|
+
}
|
|
23
|
+
// ============================================================
|
|
24
|
+
// Route Registration
|
|
25
|
+
// ============================================================
|
|
26
|
+
/**
|
|
27
|
+
* Register all agent-related routes on the Fastify instance.
|
|
28
|
+
*/
|
|
29
|
+
function registerAgentRoutes(fastify, options) {
|
|
30
|
+
const { streamManager, chatService } = options;
|
|
31
|
+
// ============================================================
|
|
32
|
+
// Engine Routes
|
|
33
|
+
// ============================================================
|
|
34
|
+
fastify.get('/agent/engines', async (_request, reply) => {
|
|
35
|
+
try {
|
|
36
|
+
const engines = chatService.getEngineInfos();
|
|
37
|
+
reply.status(constant_1.HTTP_STATUS.OK).send({ engines });
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
fastify.log.error({ err: error }, 'Failed to list agent engines');
|
|
41
|
+
if (!reply.sent) {
|
|
42
|
+
reply
|
|
43
|
+
.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR)
|
|
44
|
+
.send({ error: constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
// ============================================================
|
|
49
|
+
// Project Routes
|
|
50
|
+
// ============================================================
|
|
51
|
+
fastify.get('/agent/projects', async (_request, reply) => {
|
|
52
|
+
try {
|
|
53
|
+
const projects = await (0, project_service_1.listProjects)();
|
|
54
|
+
reply.status(constant_1.HTTP_STATUS.OK).send({ projects });
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (!reply.sent) {
|
|
58
|
+
reply
|
|
59
|
+
.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR)
|
|
60
|
+
.send({ error: constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
fastify.post('/agent/projects', async (request, reply) => {
|
|
65
|
+
try {
|
|
66
|
+
const body = request.body;
|
|
67
|
+
if (!body || !body.name || !body.rootPath) {
|
|
68
|
+
reply
|
|
69
|
+
.status(constant_1.HTTP_STATUS.BAD_REQUEST)
|
|
70
|
+
.send({ error: 'name and rootPath are required to create a project' });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const project = await (0, project_service_1.upsertProject)(body);
|
|
74
|
+
reply.status(constant_1.HTTP_STATUS.OK).send({ project });
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
78
|
+
reply
|
|
79
|
+
.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR)
|
|
80
|
+
.send({ error: message || constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR });
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
fastify.delete('/agent/projects/:id', async (request, reply) => {
|
|
84
|
+
const { id } = request.params;
|
|
85
|
+
if (!id) {
|
|
86
|
+
reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({ error: 'project id is required' });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
await (0, project_service_1.deleteProject)(id);
|
|
91
|
+
reply.status(constant_1.HTTP_STATUS.NO_CONTENT).send();
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (!reply.sent) {
|
|
95
|
+
reply
|
|
96
|
+
.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR)
|
|
97
|
+
.send({ error: constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
// Path validation API
|
|
102
|
+
fastify.post('/agent/projects/validate-path', async (request, reply) => {
|
|
103
|
+
const { rootPath } = request.body || {};
|
|
104
|
+
if (!rootPath || typeof rootPath !== 'string') {
|
|
105
|
+
return reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({ error: 'rootPath is required' });
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const result = await (0, project_service_1.validateRootPath)(rootPath);
|
|
109
|
+
return reply.send(result);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
113
|
+
return reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({ error: message });
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
// Create directory API
|
|
117
|
+
fastify.post('/agent/projects/create-directory', async (request, reply) => {
|
|
118
|
+
const { absolutePath } = request.body || {};
|
|
119
|
+
if (!absolutePath || typeof absolutePath !== 'string') {
|
|
120
|
+
return reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({ error: 'absolutePath is required' });
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
await (0, project_service_1.createProjectDirectory)(absolutePath);
|
|
124
|
+
return reply.send({ success: true, path: absolutePath });
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
128
|
+
return reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({ error: message });
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
// Get default workspace directory
|
|
132
|
+
fastify.get('/agent/projects/default-workspace', async (_request, reply) => {
|
|
133
|
+
try {
|
|
134
|
+
const workspaceDir = (0, storage_1.getDefaultWorkspaceDir)();
|
|
135
|
+
return reply.send({ success: true, path: workspaceDir });
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
139
|
+
return reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({ error: message });
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
// Get default project root for a given project name
|
|
143
|
+
fastify.post('/agent/projects/default-root', async (request, reply) => {
|
|
144
|
+
const { projectName } = request.body || {};
|
|
145
|
+
if (!projectName || typeof projectName !== 'string') {
|
|
146
|
+
return reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({ error: 'projectName is required' });
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
const rootPath = (0, storage_1.getDefaultProjectRoot)(projectName);
|
|
150
|
+
return reply.send({ success: true, path: rootPath });
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
154
|
+
return reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({ error: message });
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
// Open directory picker dialog
|
|
158
|
+
fastify.post('/agent/projects/pick-directory', async (_request, reply) => {
|
|
159
|
+
try {
|
|
160
|
+
const result = await (0, directory_picker_1.openDirectoryPicker)('Select Project Directory');
|
|
161
|
+
if (result.success && result.path) {
|
|
162
|
+
return reply.send({ success: true, path: result.path });
|
|
163
|
+
}
|
|
164
|
+
else if (result.cancelled) {
|
|
165
|
+
return reply.send({ success: false, cancelled: true });
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
return reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({
|
|
169
|
+
success: false,
|
|
170
|
+
error: result.error || 'Failed to open directory picker',
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
176
|
+
return reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({ error: message });
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
// ============================================================
|
|
180
|
+
// Session Routes
|
|
181
|
+
// ============================================================
|
|
182
|
+
// List all sessions across all projects
|
|
183
|
+
fastify.get('/agent/sessions', async (_request, reply) => {
|
|
184
|
+
try {
|
|
185
|
+
const sessions = await (0, session_service_1.getAllSessions)();
|
|
186
|
+
return reply.status(constant_1.HTTP_STATUS.OK).send({ sessions });
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
190
|
+
fastify.log.error({ err: error }, 'Failed to list all sessions');
|
|
191
|
+
return reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({
|
|
192
|
+
error: message || constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
// List sessions for a project
|
|
197
|
+
fastify.get('/agent/projects/:projectId/sessions', async (request, reply) => {
|
|
198
|
+
const { projectId } = request.params;
|
|
199
|
+
if (!projectId) {
|
|
200
|
+
return reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({ error: 'projectId is required' });
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
const sessions = await (0, session_service_1.getSessionsByProject)(projectId);
|
|
204
|
+
return reply.status(constant_1.HTTP_STATUS.OK).send({ sessions });
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
208
|
+
fastify.log.error({ err: error }, 'Failed to list sessions');
|
|
209
|
+
return reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({
|
|
210
|
+
error: message || constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
// Create a new session for a project
|
|
215
|
+
fastify.post('/agent/projects/:projectId/sessions', async (request, reply) => {
|
|
216
|
+
const { projectId } = request.params;
|
|
217
|
+
const body = request.body || {};
|
|
218
|
+
if (!projectId) {
|
|
219
|
+
return reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({ error: 'projectId is required' });
|
|
220
|
+
}
|
|
221
|
+
if (!body.engineName) {
|
|
222
|
+
return reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({ error: 'engineName is required' });
|
|
223
|
+
}
|
|
224
|
+
if (!isValidEngineName(body.engineName)) {
|
|
225
|
+
return reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({
|
|
226
|
+
error: `Invalid engineName. Must be one of: ${VALID_ENGINE_NAMES.join(', ')}`,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
// Verify project exists
|
|
231
|
+
const project = await (0, project_service_2.getProject)(projectId);
|
|
232
|
+
if (!project) {
|
|
233
|
+
return reply.status(constant_1.HTTP_STATUS.NOT_FOUND).send({ error: 'Project not found' });
|
|
234
|
+
}
|
|
235
|
+
const session = await (0, session_service_1.createSession)(projectId, body.engineName, {
|
|
236
|
+
name: body.name,
|
|
237
|
+
model: body.model,
|
|
238
|
+
permissionMode: body.permissionMode,
|
|
239
|
+
allowDangerouslySkipPermissions: body.allowDangerouslySkipPermissions,
|
|
240
|
+
systemPromptConfig: body.systemPromptConfig,
|
|
241
|
+
optionsConfig: body.optionsConfig,
|
|
242
|
+
});
|
|
243
|
+
return reply.status(constant_1.HTTP_STATUS.CREATED).send({ session });
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
247
|
+
fastify.log.error({ err: error }, 'Failed to create session');
|
|
248
|
+
return reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({
|
|
249
|
+
error: message || constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
// Get a specific session
|
|
254
|
+
fastify.get('/agent/sessions/:sessionId', async (request, reply) => {
|
|
255
|
+
const { sessionId } = request.params;
|
|
256
|
+
if (!sessionId) {
|
|
257
|
+
return reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({ error: 'sessionId is required' });
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
const session = await (0, session_service_1.getSession)(sessionId);
|
|
261
|
+
if (!session) {
|
|
262
|
+
return reply.status(constant_1.HTTP_STATUS.NOT_FOUND).send({ error: 'Session not found' });
|
|
263
|
+
}
|
|
264
|
+
return reply.status(constant_1.HTTP_STATUS.OK).send({ session });
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
268
|
+
fastify.log.error({ err: error }, 'Failed to get session');
|
|
269
|
+
return reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({
|
|
270
|
+
error: message || constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
// Update a session
|
|
275
|
+
fastify.patch('/agent/sessions/:sessionId', async (request, reply) => {
|
|
276
|
+
const { sessionId } = request.params;
|
|
277
|
+
const updates = request.body || {};
|
|
278
|
+
if (!sessionId) {
|
|
279
|
+
return reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({ error: 'sessionId is required' });
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
const existing = await (0, session_service_1.getSession)(sessionId);
|
|
283
|
+
if (!existing) {
|
|
284
|
+
return reply.status(constant_1.HTTP_STATUS.NOT_FOUND).send({ error: 'Session not found' });
|
|
285
|
+
}
|
|
286
|
+
await (0, session_service_1.updateSession)(sessionId, updates);
|
|
287
|
+
const updated = await (0, session_service_1.getSession)(sessionId);
|
|
288
|
+
return reply.status(constant_1.HTTP_STATUS.OK).send({ session: updated });
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
292
|
+
fastify.log.error({ err: error }, 'Failed to update session');
|
|
293
|
+
return reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({
|
|
294
|
+
error: message || constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
// Delete a session
|
|
299
|
+
fastify.delete('/agent/sessions/:sessionId', async (request, reply) => {
|
|
300
|
+
const { sessionId } = request.params;
|
|
301
|
+
if (!sessionId) {
|
|
302
|
+
return reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({ error: 'sessionId is required' });
|
|
303
|
+
}
|
|
304
|
+
try {
|
|
305
|
+
await (0, session_service_1.deleteSession)(sessionId);
|
|
306
|
+
return reply.status(constant_1.HTTP_STATUS.NO_CONTENT).send();
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
310
|
+
fastify.log.error({ err: error }, 'Failed to delete session');
|
|
311
|
+
return reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({
|
|
312
|
+
error: message || constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
// Get message history for a session
|
|
317
|
+
fastify.get('/agent/sessions/:sessionId/history', async (request, reply) => {
|
|
318
|
+
const { sessionId } = request.params;
|
|
319
|
+
if (!sessionId) {
|
|
320
|
+
return reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({ error: 'sessionId is required' });
|
|
321
|
+
}
|
|
322
|
+
const limitRaw = request.query.limit;
|
|
323
|
+
const offsetRaw = request.query.offset;
|
|
324
|
+
const limit = Number.parseInt(limitRaw || '', 10);
|
|
325
|
+
const offset = Number.parseInt(offsetRaw || '', 10);
|
|
326
|
+
const safeLimit = Number.isFinite(limit) && limit > 0 ? limit : 0;
|
|
327
|
+
const safeOffset = Number.isFinite(offset) && offset >= 0 ? offset : 0;
|
|
328
|
+
try {
|
|
329
|
+
const session = await (0, session_service_1.getSession)(sessionId);
|
|
330
|
+
if (!session) {
|
|
331
|
+
return reply.status(constant_1.HTTP_STATUS.NOT_FOUND).send({ error: 'Session not found' });
|
|
332
|
+
}
|
|
333
|
+
const [messages, totalCount] = await Promise.all([
|
|
334
|
+
(0, message_service_1.getMessagesBySessionId)(sessionId, safeLimit, safeOffset),
|
|
335
|
+
(0, message_service_1.getMessagesCountBySessionId)(sessionId),
|
|
336
|
+
]);
|
|
337
|
+
return reply.status(constant_1.HTTP_STATUS.OK).send({
|
|
338
|
+
success: true,
|
|
339
|
+
sessionId,
|
|
340
|
+
messages,
|
|
341
|
+
totalCount,
|
|
342
|
+
pagination: {
|
|
343
|
+
limit: safeLimit,
|
|
344
|
+
offset: safeOffset,
|
|
345
|
+
count: messages.length,
|
|
346
|
+
hasMore: safeLimit > 0 ? safeOffset + messages.length < totalCount : false,
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
352
|
+
fastify.log.error({ err: error }, 'Failed to get session history');
|
|
353
|
+
return reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({
|
|
354
|
+
error: message || constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
// Reset a session conversation (clear messages + engineSessionId)
|
|
359
|
+
fastify.post('/agent/sessions/:sessionId/reset', async (request, reply) => {
|
|
360
|
+
const { sessionId } = request.params;
|
|
361
|
+
if (!sessionId) {
|
|
362
|
+
return reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({ error: 'sessionId is required' });
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
const existing = await (0, session_service_1.getSession)(sessionId);
|
|
366
|
+
if (!existing) {
|
|
367
|
+
return reply.status(constant_1.HTTP_STATUS.NOT_FOUND).send({ error: 'Session not found' });
|
|
368
|
+
}
|
|
369
|
+
// Clear resume state first, then delete messages
|
|
370
|
+
await (0, session_service_1.updateSession)(sessionId, { engineSessionId: null });
|
|
371
|
+
const deletedMessages = await (0, message_service_1.deleteMessagesBySessionId)(sessionId);
|
|
372
|
+
const updated = await (0, session_service_1.getSession)(sessionId);
|
|
373
|
+
return reply.status(constant_1.HTTP_STATUS.OK).send({
|
|
374
|
+
success: true,
|
|
375
|
+
sessionId,
|
|
376
|
+
deletedMessages,
|
|
377
|
+
clearedEngineSessionId: Boolean(existing.engineSessionId),
|
|
378
|
+
session: updated || null,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
383
|
+
fastify.log.error({ err: error }, 'Failed to reset session');
|
|
384
|
+
return reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({
|
|
385
|
+
error: message || constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
// Get Claude management info for a session
|
|
390
|
+
fastify.get('/agent/sessions/:sessionId/claude-info', async (request, reply) => {
|
|
391
|
+
const { sessionId } = request.params;
|
|
392
|
+
if (!sessionId) {
|
|
393
|
+
return reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({ error: 'sessionId is required' });
|
|
394
|
+
}
|
|
395
|
+
try {
|
|
396
|
+
const session = await (0, session_service_1.getSession)(sessionId);
|
|
397
|
+
if (!session) {
|
|
398
|
+
return reply.status(constant_1.HTTP_STATUS.NOT_FOUND).send({ error: 'Session not found' });
|
|
399
|
+
}
|
|
400
|
+
return reply.status(constant_1.HTTP_STATUS.OK).send({
|
|
401
|
+
managementInfo: session.managementInfo || null,
|
|
402
|
+
sessionId,
|
|
403
|
+
engineName: session.engineName,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
408
|
+
fastify.log.error({ err: error }, 'Failed to get Claude info');
|
|
409
|
+
return reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({
|
|
410
|
+
error: message || constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
// Get aggregated Claude management info for a project
|
|
415
|
+
// Returns the most recent management info from any Claude session in the project
|
|
416
|
+
fastify.get('/agent/projects/:projectId/claude-info', async (request, reply) => {
|
|
417
|
+
var _a, _b;
|
|
418
|
+
const { projectId } = request.params;
|
|
419
|
+
if (!projectId) {
|
|
420
|
+
return reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({ error: 'projectId is required' });
|
|
421
|
+
}
|
|
422
|
+
try {
|
|
423
|
+
const project = await (0, project_service_2.getProject)(projectId);
|
|
424
|
+
if (!project) {
|
|
425
|
+
return reply.status(constant_1.HTTP_STATUS.NOT_FOUND).send({ error: 'Project not found' });
|
|
426
|
+
}
|
|
427
|
+
// Get only Claude sessions (more efficient than fetching all and filtering)
|
|
428
|
+
const claudeSessions = await (0, session_service_1.getSessionsByProjectAndEngine)(projectId, 'claude');
|
|
429
|
+
const sessionsWithInfo = claudeSessions.filter((s) => s.managementInfo);
|
|
430
|
+
// Sort by lastUpdated in management info (fallback to session.updatedAt for old data)
|
|
431
|
+
sessionsWithInfo.sort((a, b) => {
|
|
432
|
+
var _a, _b;
|
|
433
|
+
const aTime = ((_a = a.managementInfo) === null || _a === void 0 ? void 0 : _a.lastUpdated) || a.updatedAt || '';
|
|
434
|
+
const bTime = ((_b = b.managementInfo) === null || _b === void 0 ? void 0 : _b.lastUpdated) || b.updatedAt || '';
|
|
435
|
+
return bTime.localeCompare(aTime);
|
|
436
|
+
});
|
|
437
|
+
const latestInfo = ((_a = sessionsWithInfo[0]) === null || _a === void 0 ? void 0 : _a.managementInfo) || null;
|
|
438
|
+
const sourceSessionId = (_b = sessionsWithInfo[0]) === null || _b === void 0 ? void 0 : _b.id;
|
|
439
|
+
return reply.status(constant_1.HTTP_STATUS.OK).send({
|
|
440
|
+
managementInfo: latestInfo,
|
|
441
|
+
sourceSessionId,
|
|
442
|
+
projectId,
|
|
443
|
+
sessionsWithInfo: sessionsWithInfo.length,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
catch (error) {
|
|
447
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
448
|
+
fastify.log.error({ err: error }, 'Failed to get project Claude info');
|
|
449
|
+
return reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({
|
|
450
|
+
error: message || constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
// ============================================================
|
|
455
|
+
// Open Project Routes
|
|
456
|
+
// ============================================================
|
|
457
|
+
/**
|
|
458
|
+
* POST /agent/sessions/:sessionId/open
|
|
459
|
+
* Open session's project directory in VSCode or terminal.
|
|
460
|
+
*/
|
|
461
|
+
fastify.post('/agent/sessions/:sessionId/open', async (request, reply) => {
|
|
462
|
+
const { sessionId } = request.params;
|
|
463
|
+
const { target } = request.body || {};
|
|
464
|
+
if (!sessionId) {
|
|
465
|
+
return reply
|
|
466
|
+
.status(constant_1.HTTP_STATUS.BAD_REQUEST)
|
|
467
|
+
.send({ success: false, error: 'sessionId is required' });
|
|
468
|
+
}
|
|
469
|
+
if (!target || typeof target !== 'string') {
|
|
470
|
+
return reply
|
|
471
|
+
.status(constant_1.HTTP_STATUS.BAD_REQUEST)
|
|
472
|
+
.send({ success: false, error: 'target is required' });
|
|
473
|
+
}
|
|
474
|
+
if (!isValidOpenTarget(target)) {
|
|
475
|
+
return reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({
|
|
476
|
+
success: false,
|
|
477
|
+
error: `Invalid target. Must be one of: ${VALID_OPEN_TARGETS.join(', ')}`,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
try {
|
|
481
|
+
// Get session and its project
|
|
482
|
+
const session = await (0, session_service_1.getSession)(sessionId);
|
|
483
|
+
if (!session) {
|
|
484
|
+
return reply
|
|
485
|
+
.status(constant_1.HTTP_STATUS.NOT_FOUND)
|
|
486
|
+
.send({ success: false, error: 'Session not found' });
|
|
487
|
+
}
|
|
488
|
+
const project = await (0, project_service_2.getProject)(session.projectId);
|
|
489
|
+
if (!project) {
|
|
490
|
+
return reply
|
|
491
|
+
.status(constant_1.HTTP_STATUS.NOT_FOUND)
|
|
492
|
+
.send({ success: false, error: 'Project not found' });
|
|
493
|
+
}
|
|
494
|
+
// Open the project directory
|
|
495
|
+
const result = await (0, open_project_1.openProjectDirectory)(project.rootPath, target);
|
|
496
|
+
if (result.success) {
|
|
497
|
+
return reply.status(constant_1.HTTP_STATUS.OK).send({ success: true });
|
|
498
|
+
}
|
|
499
|
+
return reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({
|
|
500
|
+
success: false,
|
|
501
|
+
error: result.error,
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
catch (error) {
|
|
505
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
506
|
+
fastify.log.error({ err: error }, 'Failed to open session project');
|
|
507
|
+
return reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({
|
|
508
|
+
success: false,
|
|
509
|
+
error: message || constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
/**
|
|
514
|
+
* POST /agent/projects/:projectId/open
|
|
515
|
+
* Open project directory in VSCode or terminal.
|
|
516
|
+
*/
|
|
517
|
+
fastify.post('/agent/projects/:projectId/open', async (request, reply) => {
|
|
518
|
+
const { projectId } = request.params;
|
|
519
|
+
const { target } = request.body || {};
|
|
520
|
+
if (!projectId) {
|
|
521
|
+
return reply
|
|
522
|
+
.status(constant_1.HTTP_STATUS.BAD_REQUEST)
|
|
523
|
+
.send({ success: false, error: 'projectId is required' });
|
|
524
|
+
}
|
|
525
|
+
if (!target || typeof target !== 'string') {
|
|
526
|
+
return reply
|
|
527
|
+
.status(constant_1.HTTP_STATUS.BAD_REQUEST)
|
|
528
|
+
.send({ success: false, error: 'target is required' });
|
|
529
|
+
}
|
|
530
|
+
if (!isValidOpenTarget(target)) {
|
|
531
|
+
return reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({
|
|
532
|
+
success: false,
|
|
533
|
+
error: `Invalid target. Must be one of: ${VALID_OPEN_TARGETS.join(', ')}`,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
try {
|
|
537
|
+
const project = await (0, project_service_2.getProject)(projectId);
|
|
538
|
+
if (!project) {
|
|
539
|
+
return reply
|
|
540
|
+
.status(constant_1.HTTP_STATUS.NOT_FOUND)
|
|
541
|
+
.send({ success: false, error: 'Project not found' });
|
|
542
|
+
}
|
|
543
|
+
// Open the project directory
|
|
544
|
+
const result = await (0, open_project_1.openProjectDirectory)(project.rootPath, target);
|
|
545
|
+
if (result.success) {
|
|
546
|
+
return reply.status(constant_1.HTTP_STATUS.OK).send({ success: true });
|
|
547
|
+
}
|
|
548
|
+
return reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({
|
|
549
|
+
success: false,
|
|
550
|
+
error: result.error,
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
catch (error) {
|
|
554
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
555
|
+
fastify.log.error({ err: error }, 'Failed to open project');
|
|
556
|
+
return reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({
|
|
557
|
+
success: false,
|
|
558
|
+
error: message || constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR,
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
/**
|
|
563
|
+
* POST /agent/projects/:projectId/open-file
|
|
564
|
+
* Open a file in VSCode at a specific line/column.
|
|
565
|
+
*
|
|
566
|
+
* Request body:
|
|
567
|
+
* - filePath: string (required) - File path (relative or absolute)
|
|
568
|
+
* - line?: number - Line number (1-based)
|
|
569
|
+
* - column?: number - Column number (1-based)
|
|
570
|
+
*/
|
|
571
|
+
fastify.post('/agent/projects/:projectId/open-file', async (request, reply) => {
|
|
572
|
+
const { projectId } = request.params;
|
|
573
|
+
const { filePath, line, column } = request.body || {};
|
|
574
|
+
if (!projectId) {
|
|
575
|
+
return reply
|
|
576
|
+
.status(constant_1.HTTP_STATUS.BAD_REQUEST)
|
|
577
|
+
.send({ success: false, error: 'projectId is required' });
|
|
578
|
+
}
|
|
579
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
580
|
+
return reply
|
|
581
|
+
.status(constant_1.HTTP_STATUS.BAD_REQUEST)
|
|
582
|
+
.send({ success: false, error: 'filePath is required' });
|
|
583
|
+
}
|
|
584
|
+
try {
|
|
585
|
+
const project = await (0, project_service_2.getProject)(projectId);
|
|
586
|
+
if (!project) {
|
|
587
|
+
return reply
|
|
588
|
+
.status(constant_1.HTTP_STATUS.NOT_FOUND)
|
|
589
|
+
.send({ success: false, error: 'Project not found' });
|
|
590
|
+
}
|
|
591
|
+
// Open the file in VSCode
|
|
592
|
+
const result = await (0, open_project_1.openFileInVSCode)(project.rootPath, filePath, line, column);
|
|
593
|
+
if (result.success) {
|
|
594
|
+
return reply.status(constant_1.HTTP_STATUS.OK).send({ success: true });
|
|
595
|
+
}
|
|
596
|
+
return reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({
|
|
597
|
+
success: false,
|
|
598
|
+
error: result.error,
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
catch (error) {
|
|
602
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
603
|
+
fastify.log.error({ err: error }, 'Failed to open file in VSCode');
|
|
604
|
+
return reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({
|
|
605
|
+
success: false,
|
|
606
|
+
error: message || constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR,
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
// ============================================================
|
|
611
|
+
// Chat Message Routes
|
|
612
|
+
// ============================================================
|
|
613
|
+
fastify.get('/agent/chat/:projectId/messages', async (request, reply) => {
|
|
614
|
+
const { projectId } = request.params;
|
|
615
|
+
if (!projectId) {
|
|
616
|
+
reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({ error: 'projectId is required' });
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const limitRaw = request.query.limit;
|
|
620
|
+
const offsetRaw = request.query.offset;
|
|
621
|
+
const limit = Number.parseInt(limitRaw || '', 10);
|
|
622
|
+
const offset = Number.parseInt(offsetRaw || '', 10);
|
|
623
|
+
const safeLimit = Number.isFinite(limit) && limit > 0 ? limit : 50;
|
|
624
|
+
const safeOffset = Number.isFinite(offset) && offset >= 0 ? offset : 0;
|
|
625
|
+
try {
|
|
626
|
+
const [messages, totalCount] = await Promise.all([
|
|
627
|
+
(0, message_service_1.getMessagesByProjectId)(projectId, safeLimit, safeOffset),
|
|
628
|
+
(0, message_service_1.getMessagesCountByProjectId)(projectId),
|
|
629
|
+
]);
|
|
630
|
+
reply.status(constant_1.HTTP_STATUS.OK).send({
|
|
631
|
+
success: true,
|
|
632
|
+
data: messages,
|
|
633
|
+
totalCount,
|
|
634
|
+
pagination: {
|
|
635
|
+
limit: safeLimit,
|
|
636
|
+
offset: safeOffset,
|
|
637
|
+
count: messages.length,
|
|
638
|
+
hasMore: safeOffset + messages.length < totalCount,
|
|
639
|
+
},
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
catch (error) {
|
|
643
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
644
|
+
fastify.log.error({ err: error }, 'Failed to load agent chat messages');
|
|
645
|
+
reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({
|
|
646
|
+
success: false,
|
|
647
|
+
error: 'Failed to fetch messages',
|
|
648
|
+
message: message || constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR,
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
fastify.post('/agent/chat/:projectId/messages', async (request, reply) => {
|
|
653
|
+
const { projectId } = request.params;
|
|
654
|
+
if (!projectId) {
|
|
655
|
+
reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({ error: 'projectId is required' });
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
const body = request.body || {};
|
|
659
|
+
const content = typeof body.content === 'string' ? body.content.trim() : '';
|
|
660
|
+
if (!content) {
|
|
661
|
+
reply
|
|
662
|
+
.status(constant_1.HTTP_STATUS.BAD_REQUEST)
|
|
663
|
+
.send({ success: false, error: 'content is required' });
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
const rawRole = typeof body.role === 'string' ? body.role.toLowerCase().trim() : 'user';
|
|
667
|
+
const role = rawRole === 'assistant' || rawRole === 'system' || rawRole === 'tool'
|
|
668
|
+
? rawRole
|
|
669
|
+
: 'user';
|
|
670
|
+
const rawType = typeof body.messageType === 'string' ? body.messageType.toLowerCase() : '';
|
|
671
|
+
const allowedTypes = ['chat', 'tool_use', 'tool_result', 'status'];
|
|
672
|
+
const fallbackType = role === 'system' ? 'status' : 'chat';
|
|
673
|
+
const messageType = allowedTypes.includes(rawType) && rawType
|
|
674
|
+
? rawType
|
|
675
|
+
: fallbackType;
|
|
676
|
+
try {
|
|
677
|
+
const stored = await (0, message_service_1.createMessage)({
|
|
678
|
+
projectId,
|
|
679
|
+
role,
|
|
680
|
+
messageType,
|
|
681
|
+
content,
|
|
682
|
+
metadata: body.metadata,
|
|
683
|
+
sessionId: body.sessionId,
|
|
684
|
+
conversationId: body.conversationId,
|
|
685
|
+
cliSource: body.cliSource,
|
|
686
|
+
requestId: body.requestId,
|
|
687
|
+
id: body.id,
|
|
688
|
+
createdAt: body.createdAt,
|
|
689
|
+
});
|
|
690
|
+
reply.status(constant_1.HTTP_STATUS.CREATED).send({ success: true, data: stored });
|
|
691
|
+
}
|
|
692
|
+
catch (error) {
|
|
693
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
694
|
+
fastify.log.error({ err: error }, 'Failed to create agent chat message');
|
|
695
|
+
reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({
|
|
696
|
+
success: false,
|
|
697
|
+
error: 'Failed to create message',
|
|
698
|
+
message: message || constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR,
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
fastify.delete('/agent/chat/:projectId/messages', async (request, reply) => {
|
|
703
|
+
const { projectId } = request.params;
|
|
704
|
+
if (!projectId) {
|
|
705
|
+
reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({ error: 'projectId is required' });
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
const { conversationId } = request.query;
|
|
709
|
+
try {
|
|
710
|
+
const deleted = await (0, message_service_1.deleteMessagesByProjectId)(projectId, conversationId || undefined);
|
|
711
|
+
reply.status(constant_1.HTTP_STATUS.OK).send({ success: true, deleted });
|
|
712
|
+
}
|
|
713
|
+
catch (error) {
|
|
714
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
715
|
+
fastify.log.error({ err: error }, 'Failed to delete agent chat messages');
|
|
716
|
+
reply.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send({
|
|
717
|
+
success: false,
|
|
718
|
+
error: 'Failed to delete messages',
|
|
719
|
+
message: message || constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR,
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
});
|
|
723
|
+
// ============================================================
|
|
724
|
+
// Chat Streaming Routes (SSE)
|
|
725
|
+
// ============================================================
|
|
726
|
+
fastify.get('/agent/chat/:sessionId/stream', async (request, reply) => {
|
|
727
|
+
const { sessionId } = request.params;
|
|
728
|
+
if (!sessionId) {
|
|
729
|
+
reply
|
|
730
|
+
.status(constant_1.HTTP_STATUS.BAD_REQUEST)
|
|
731
|
+
.send({ error: 'sessionId is required for agent stream' });
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
try {
|
|
735
|
+
reply.raw.writeHead(constant_1.HTTP_STATUS.OK, {
|
|
736
|
+
'Content-Type': 'text/event-stream',
|
|
737
|
+
'Cache-Control': 'no-cache',
|
|
738
|
+
Connection: 'keep-alive',
|
|
739
|
+
});
|
|
740
|
+
// Ensure client immediately receives an open event
|
|
741
|
+
reply.raw.write(':\n\n');
|
|
742
|
+
streamManager.addSseStream(sessionId, reply.raw);
|
|
743
|
+
const connectedEvent = {
|
|
744
|
+
type: 'connected',
|
|
745
|
+
data: {
|
|
746
|
+
sessionId,
|
|
747
|
+
transport: 'sse',
|
|
748
|
+
timestamp: new Date().toISOString(),
|
|
749
|
+
},
|
|
750
|
+
};
|
|
751
|
+
streamManager.publish(connectedEvent);
|
|
752
|
+
reply.raw.on('close', () => {
|
|
753
|
+
streamManager.removeSseStream(sessionId, reply.raw);
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
catch (error) {
|
|
757
|
+
if (!reply.sent) {
|
|
758
|
+
reply.code(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR).send(constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
// ============================================================
|
|
763
|
+
// Chat Action Routes
|
|
764
|
+
// ============================================================
|
|
765
|
+
fastify.post('/agent/chat/:sessionId/act', {
|
|
766
|
+
// Increase body limit to support image attachments (base64 encoded)
|
|
767
|
+
// Default Fastify limit is 1MB, which is too small for images
|
|
768
|
+
config: {
|
|
769
|
+
rawBody: false,
|
|
770
|
+
},
|
|
771
|
+
bodyLimit: 50 * 1024 * 1024, // 50MB to support multiple images
|
|
772
|
+
}, async (request, reply) => {
|
|
773
|
+
const { sessionId } = request.params;
|
|
774
|
+
const payload = request.body;
|
|
775
|
+
if (!sessionId) {
|
|
776
|
+
reply
|
|
777
|
+
.status(constant_1.HTTP_STATUS.BAD_REQUEST)
|
|
778
|
+
.send({ error: 'sessionId is required for agent act' });
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
try {
|
|
782
|
+
const { requestId } = await chatService.handleAct(sessionId, payload);
|
|
783
|
+
const response = {
|
|
784
|
+
requestId,
|
|
785
|
+
sessionId,
|
|
786
|
+
status: 'accepted',
|
|
787
|
+
};
|
|
788
|
+
reply.status(constant_1.HTTP_STATUS.OK).send(response);
|
|
789
|
+
}
|
|
790
|
+
catch (error) {
|
|
791
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
792
|
+
reply
|
|
793
|
+
.status(constant_1.HTTP_STATUS.BAD_REQUEST)
|
|
794
|
+
.send({ error: message || constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR });
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
// Cancel specific request
|
|
798
|
+
fastify.delete('/agent/chat/:sessionId/cancel/:requestId', async (request, reply) => {
|
|
799
|
+
const { sessionId, requestId } = request.params;
|
|
800
|
+
if (!sessionId || !requestId) {
|
|
801
|
+
reply
|
|
802
|
+
.status(constant_1.HTTP_STATUS.BAD_REQUEST)
|
|
803
|
+
.send({ error: 'sessionId and requestId are required' });
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
const cancelled = chatService.cancelExecution(requestId);
|
|
807
|
+
if (cancelled) {
|
|
808
|
+
reply.status(constant_1.HTTP_STATUS.OK).send({
|
|
809
|
+
success: true,
|
|
810
|
+
message: 'Execution cancelled',
|
|
811
|
+
requestId,
|
|
812
|
+
sessionId,
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
else {
|
|
816
|
+
reply.status(constant_1.HTTP_STATUS.OK).send({
|
|
817
|
+
success: false,
|
|
818
|
+
message: 'No running execution found with this requestId',
|
|
819
|
+
requestId,
|
|
820
|
+
sessionId,
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
// Cancel all executions for a session
|
|
825
|
+
fastify.delete('/agent/chat/:sessionId/cancel', async (request, reply) => {
|
|
826
|
+
const { sessionId } = request.params;
|
|
827
|
+
if (!sessionId) {
|
|
828
|
+
reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({ error: 'sessionId is required' });
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
const cancelledCount = chatService.cancelSessionExecutions(sessionId);
|
|
832
|
+
reply.status(constant_1.HTTP_STATUS.OK).send({
|
|
833
|
+
success: true,
|
|
834
|
+
cancelledCount,
|
|
835
|
+
sessionId,
|
|
836
|
+
});
|
|
837
|
+
});
|
|
838
|
+
// ============================================================
|
|
839
|
+
// Attachment Routes
|
|
840
|
+
// ============================================================
|
|
841
|
+
/**
|
|
842
|
+
* GET /agent/attachments/stats
|
|
843
|
+
* Get statistics for all attachment caches.
|
|
844
|
+
*/
|
|
845
|
+
fastify.get('/agent/attachments/stats', async (_request, reply) => {
|
|
846
|
+
try {
|
|
847
|
+
const stats = await attachment_service_1.attachmentService.getAttachmentStats();
|
|
848
|
+
// Enrich with project names from database
|
|
849
|
+
const projects = await (0, project_service_1.listProjects)();
|
|
850
|
+
const projectMap = new Map(projects.map((p) => [p.id, p.name]));
|
|
851
|
+
const dbProjectIds = new Set(projects.map((p) => p.id));
|
|
852
|
+
const enrichedProjects = stats.projects.map((p) => ({
|
|
853
|
+
...p,
|
|
854
|
+
projectName: projectMap.get(p.projectId),
|
|
855
|
+
existsInDb: dbProjectIds.has(p.projectId),
|
|
856
|
+
}));
|
|
857
|
+
const orphanProjectIds = stats.projects
|
|
858
|
+
.filter((p) => !dbProjectIds.has(p.projectId))
|
|
859
|
+
.map((p) => p.projectId);
|
|
860
|
+
const response = {
|
|
861
|
+
success: true,
|
|
862
|
+
rootDir: stats.rootDir,
|
|
863
|
+
totalFiles: stats.totalFiles,
|
|
864
|
+
totalBytes: stats.totalBytes,
|
|
865
|
+
projects: enrichedProjects,
|
|
866
|
+
orphanProjectIds,
|
|
867
|
+
};
|
|
868
|
+
reply.status(constant_1.HTTP_STATUS.OK).send(response);
|
|
869
|
+
}
|
|
870
|
+
catch (error) {
|
|
871
|
+
fastify.log.error({ err: error }, 'Failed to get attachment stats');
|
|
872
|
+
reply
|
|
873
|
+
.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR)
|
|
874
|
+
.send({ error: constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR });
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
/**
|
|
878
|
+
* GET /agent/attachments/:projectId/:filename
|
|
879
|
+
* Serve an attachment file.
|
|
880
|
+
*/
|
|
881
|
+
fastify.get('/agent/attachments/:projectId/:filename', async (request, reply) => {
|
|
882
|
+
var _a;
|
|
883
|
+
const { projectId, filename } = request.params;
|
|
884
|
+
try {
|
|
885
|
+
// Validate and get file
|
|
886
|
+
const buffer = await attachment_service_1.attachmentService.readAttachment(projectId, filename);
|
|
887
|
+
// Determine content type from filename extension
|
|
888
|
+
const ext = (_a = filename.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase();
|
|
889
|
+
let contentType = 'application/octet-stream';
|
|
890
|
+
switch (ext) {
|
|
891
|
+
case 'png':
|
|
892
|
+
contentType = 'image/png';
|
|
893
|
+
break;
|
|
894
|
+
case 'jpg':
|
|
895
|
+
case 'jpeg':
|
|
896
|
+
contentType = 'image/jpeg';
|
|
897
|
+
break;
|
|
898
|
+
case 'gif':
|
|
899
|
+
contentType = 'image/gif';
|
|
900
|
+
break;
|
|
901
|
+
case 'webp':
|
|
902
|
+
contentType = 'image/webp';
|
|
903
|
+
break;
|
|
904
|
+
}
|
|
905
|
+
reply
|
|
906
|
+
.header('Content-Type', contentType)
|
|
907
|
+
.header('Cache-Control', 'public, max-age=31536000, immutable')
|
|
908
|
+
.send(buffer);
|
|
909
|
+
}
|
|
910
|
+
catch (error) {
|
|
911
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
912
|
+
if (message.includes('Invalid') || message.includes('traversal')) {
|
|
913
|
+
reply.status(constant_1.HTTP_STATUS.BAD_REQUEST).send({ error: message });
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
// File not found or read error
|
|
917
|
+
reply.status(constant_1.HTTP_STATUS.NOT_FOUND).send({ error: 'Attachment not found' });
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
/**
|
|
921
|
+
* DELETE /agent/attachments/:projectId
|
|
922
|
+
* Clean up attachments for a specific project.
|
|
923
|
+
*/
|
|
924
|
+
fastify.delete('/agent/attachments/:projectId', async (request, reply) => {
|
|
925
|
+
const { projectId } = request.params;
|
|
926
|
+
try {
|
|
927
|
+
const result = await attachment_service_1.attachmentService.cleanupAttachments({ projectIds: [projectId] });
|
|
928
|
+
const response = {
|
|
929
|
+
success: true,
|
|
930
|
+
scope: 'project',
|
|
931
|
+
removedFiles: result.removedFiles,
|
|
932
|
+
removedBytes: result.removedBytes,
|
|
933
|
+
results: result.results,
|
|
934
|
+
};
|
|
935
|
+
reply.status(constant_1.HTTP_STATUS.OK).send(response);
|
|
936
|
+
}
|
|
937
|
+
catch (error) {
|
|
938
|
+
fastify.log.error({ err: error }, 'Failed to cleanup project attachments');
|
|
939
|
+
reply
|
|
940
|
+
.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR)
|
|
941
|
+
.send({ error: constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR });
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
/**
|
|
945
|
+
* DELETE /agent/attachments
|
|
946
|
+
* Clean up attachments for all or selected projects.
|
|
947
|
+
*/
|
|
948
|
+
fastify.delete('/agent/attachments', async (request, reply) => {
|
|
949
|
+
try {
|
|
950
|
+
const body = request.body;
|
|
951
|
+
const projectIds = body === null || body === void 0 ? void 0 : body.projectIds;
|
|
952
|
+
const result = await attachment_service_1.attachmentService.cleanupAttachments(projectIds ? { projectIds } : undefined);
|
|
953
|
+
const scope = projectIds && projectIds.length > 0 ? 'selected' : 'all';
|
|
954
|
+
const response = {
|
|
955
|
+
success: true,
|
|
956
|
+
scope,
|
|
957
|
+
removedFiles: result.removedFiles,
|
|
958
|
+
removedBytes: result.removedBytes,
|
|
959
|
+
results: result.results,
|
|
960
|
+
};
|
|
961
|
+
reply.status(constant_1.HTTP_STATUS.OK).send(response);
|
|
962
|
+
}
|
|
963
|
+
catch (error) {
|
|
964
|
+
fastify.log.error({ err: error }, 'Failed to cleanup attachments');
|
|
965
|
+
reply
|
|
966
|
+
.status(constant_1.HTTP_STATUS.INTERNAL_SERVER_ERROR)
|
|
967
|
+
.send({ error: constant_1.ERROR_MESSAGES.INTERNAL_SERVER_ERROR });
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
//# sourceMappingURL=agent.js.map
|