devchain-cli 0.2.1 → 0.3.1
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 +2 -0
- package/dist/cli.js +199 -42
- package/dist/drizzle/0018_whole_zodiak.sql +43 -0
- package/dist/drizzle/0019_flat_avengers.sql +3 -0
- package/dist/drizzle/0020_statuses_mcp_hidden.sql +1 -0
- package/dist/drizzle/meta/0018_snapshot.json +2920 -0
- package/dist/drizzle/meta/0019_snapshot.json +2943 -0
- package/dist/drizzle/meta/0020_snapshot.json +2951 -0
- package/dist/drizzle/meta/_journal.json +21 -0
- package/dist/server/app.module.js +11 -1
- package/dist/server/app.module.js.map +1 -1
- package/dist/server/common/config/env.config.d.ts +1 -0
- package/dist/server/common/config/env.config.js +4 -0
- package/dist/server/common/config/env.config.js.map +1 -1
- package/dist/server/common/filters/http-exception.filter.js +24 -1
- package/dist/server/common/filters/http-exception.filter.js.map +1 -1
- package/dist/server/common/logging/logger.js +4 -3
- package/dist/server/common/logging/logger.js.map +1 -1
- package/dist/server/main.js +0 -3
- package/dist/server/main.js.map +1 -1
- package/dist/server/modules/agents/agents.module.js +2 -1
- package/dist/server/modules/agents/agents.module.js.map +1 -1
- package/dist/server/modules/agents/controllers/agents.controller.d.ts +17 -2
- package/dist/server/modules/agents/controllers/agents.controller.js +84 -3
- package/dist/server/modules/agents/controllers/agents.controller.js.map +1 -1
- package/dist/server/modules/chat/dtos/chat.dto.d.ts +18 -18
- package/dist/server/modules/chat/services/invite-template.util.js +1 -1
- package/dist/server/modules/chat/services/invite-template.util.js.map +1 -1
- package/dist/server/modules/core/controllers/health.controller.d.ts +1 -0
- package/dist/server/modules/core/controllers/health.controller.js +23 -0
- package/dist/server/modules/core/controllers/health.controller.js.map +1 -1
- package/dist/server/modules/events/catalog/index.d.ts +40 -0
- package/dist/server/modules/events/catalog/index.js +2 -0
- package/dist/server/modules/events/catalog/index.js.map +1 -1
- package/dist/server/modules/events/catalog/terminal.watcher.triggered.d.ts +45 -0
- package/dist/server/modules/events/catalog/terminal.watcher.triggered.js +22 -0
- package/dist/server/modules/events/catalog/terminal.watcher.triggered.js.map +1 -0
- package/dist/server/modules/events/subscribers/chat-message-delivery.subscriber.d.ts +1 -0
- package/dist/server/modules/events/subscribers/chat-message-delivery.subscriber.js +30 -12
- package/dist/server/modules/events/subscribers/chat-message-delivery.subscriber.js.map +1 -1
- package/dist/server/modules/mcp/constants.js +5 -1
- package/dist/server/modules/mcp/constants.js.map +1 -1
- package/dist/server/modules/mcp/controllers/mcp-http.controller.js +78 -77
- package/dist/server/modules/mcp/controllers/mcp-http.controller.js.map +1 -1
- package/dist/server/modules/mcp/controllers/mcp-sdk.controller.js +78 -77
- package/dist/server/modules/mcp/controllers/mcp-sdk.controller.js.map +1 -1
- package/dist/server/modules/mcp/dtos/mcp.dto.d.ts +125 -97
- package/dist/server/modules/mcp/dtos/mcp.dto.js +23 -26
- package/dist/server/modules/mcp/dtos/mcp.dto.js.map +1 -1
- package/dist/server/modules/mcp/services/instructions-resolver.d.ts +3 -0
- package/dist/server/modules/mcp/services/instructions-resolver.js +83 -2
- package/dist/server/modules/mcp/services/instructions-resolver.js.map +1 -1
- package/dist/server/modules/mcp/services/mcp.service.d.ts +3 -2
- package/dist/server/modules/mcp/services/mcp.service.js +549 -263
- package/dist/server/modules/mcp/services/mcp.service.js.map +1 -1
- package/dist/server/modules/projects/controllers/projects.controller.d.ts +42 -0
- package/dist/server/modules/projects/controllers/projects.controller.js +11 -0
- package/dist/server/modules/projects/controllers/projects.controller.js.map +1 -1
- package/dist/server/modules/projects/projects.module.js +2 -1
- package/dist/server/modules/projects/projects.module.js.map +1 -1
- package/dist/server/modules/projects/services/projects.service.d.ts +47 -1
- package/dist/server/modules/projects/services/projects.service.js +278 -22
- package/dist/server/modules/projects/services/projects.service.js.map +1 -1
- package/dist/server/modules/prompts/controllers/prompts.controller.d.ts +1 -1
- package/dist/server/modules/prompts/controllers/prompts.controller.js +26 -4
- package/dist/server/modules/prompts/controllers/prompts.controller.js.map +1 -1
- package/dist/server/modules/sessions/utils/template-renderer.js +1 -0
- package/dist/server/modules/sessions/utils/template-renderer.js.map +1 -1
- package/dist/server/modules/statuses/controllers/statuses.controller.js +2 -0
- package/dist/server/modules/statuses/controllers/statuses.controller.js.map +1 -1
- package/dist/server/modules/storage/db/schema.d.ts +613 -0
- package/dist/server/modules/storage/db/schema.js +50 -1
- package/dist/server/modules/storage/db/schema.js.map +1 -1
- package/dist/server/modules/storage/interfaces/storage.interface.d.ts +40 -2
- package/dist/server/modules/storage/interfaces/storage.interface.js.map +1 -1
- package/dist/server/modules/storage/local/local-storage.service.d.ts +18 -3
- package/dist/server/modules/storage/local/local-storage.service.js +407 -11
- package/dist/server/modules/storage/local/local-storage.service.js.map +1 -1
- package/dist/server/modules/storage/models/domain.models.d.ts +59 -1
- package/dist/server/modules/subscribers/actions/action.interface.d.ts +67 -0
- package/dist/server/modules/subscribers/actions/action.interface.js +3 -0
- package/dist/server/modules/subscribers/actions/action.interface.js.map +1 -0
- package/dist/server/modules/subscribers/actions/actions.registry.d.ts +7 -0
- package/dist/server/modules/subscribers/actions/actions.registry.js +37 -0
- package/dist/server/modules/subscribers/actions/actions.registry.js.map +1 -0
- package/dist/server/modules/subscribers/actions/restart-agent.action.d.ts +8 -0
- package/dist/server/modules/subscribers/actions/restart-agent.action.js +119 -0
- package/dist/server/modules/subscribers/actions/restart-agent.action.js.map +1 -0
- package/dist/server/modules/subscribers/actions/send-message.action.d.ts +2 -0
- package/dist/server/modules/subscribers/actions/send-message.action.js +83 -0
- package/dist/server/modules/subscribers/actions/send-message.action.js.map +1 -0
- package/dist/server/modules/subscribers/controllers/actions.controller.d.ts +6 -0
- package/dist/server/modules/subscribers/controllers/actions.controller.js +51 -0
- package/dist/server/modules/subscribers/controllers/actions.controller.js.map +1 -0
- package/dist/server/modules/subscribers/controllers/subscribers.controller.d.ts +17 -0
- package/dist/server/modules/subscribers/controllers/subscribers.controller.js +178 -0
- package/dist/server/modules/subscribers/controllers/subscribers.controller.js.map +1 -0
- package/dist/server/modules/subscribers/dtos/subscriber.dto.d.ts +251 -0
- package/dist/server/modules/subscribers/dtos/subscriber.dto.js +68 -0
- package/dist/server/modules/subscribers/dtos/subscriber.dto.js.map +1 -0
- package/dist/server/modules/subscribers/events/event-fields-catalog.d.ts +19 -0
- package/dist/server/modules/subscribers/events/event-fields-catalog.js +98 -0
- package/dist/server/modules/subscribers/events/event-fields-catalog.js.map +1 -0
- package/dist/server/modules/subscribers/services/automation-scheduler.service.d.ts +49 -0
- package/dist/server/modules/subscribers/services/automation-scheduler.service.js +300 -0
- package/dist/server/modules/subscribers/services/automation-scheduler.service.js.map +1 -0
- package/dist/server/modules/subscribers/services/subscriber-executor.service.d.ts +77 -0
- package/dist/server/modules/subscribers/services/subscriber-executor.service.js +576 -0
- package/dist/server/modules/subscribers/services/subscriber-executor.service.js.map +1 -0
- package/dist/server/modules/subscribers/services/subscribers.service.d.ts +14 -0
- package/dist/server/modules/subscribers/services/subscribers.service.js +70 -0
- package/dist/server/modules/subscribers/services/subscribers.service.js.map +1 -0
- package/dist/server/modules/subscribers/subscribers.module.d.ts +2 -0
- package/dist/server/modules/subscribers/subscribers.module.js +36 -0
- package/dist/server/modules/subscribers/subscribers.module.js.map +1 -0
- package/dist/server/modules/terminal/services/tmux.service.js +9 -6
- package/dist/server/modules/terminal/services/tmux.service.js.map +1 -1
- package/dist/server/modules/watchers/controllers/watchers.controller.d.ts +16 -0
- package/dist/server/modules/watchers/controllers/watchers.controller.js +180 -0
- package/dist/server/modules/watchers/controllers/watchers.controller.js.map +1 -0
- package/dist/server/modules/watchers/dtos/watcher.dto.d.ts +206 -0
- package/dist/server/modules/watchers/dtos/watcher.dto.js +54 -0
- package/dist/server/modules/watchers/dtos/watcher.dto.js.map +1 -0
- package/dist/server/modules/watchers/services/watcher-runner.service.d.ts +68 -0
- package/dist/server/modules/watchers/services/watcher-runner.service.js +477 -0
- package/dist/server/modules/watchers/services/watcher-runner.service.js.map +1 -0
- package/dist/server/modules/watchers/services/watchers.service.d.ts +29 -0
- package/dist/server/modules/watchers/services/watchers.service.js +98 -0
- package/dist/server/modules/watchers/services/watchers.service.js.map +1 -0
- package/dist/server/modules/watchers/watchers.module.d.ts +2 -0
- package/dist/server/modules/watchers/watchers.module.js +34 -0
- package/dist/server/modules/watchers/watchers.module.js.map +1 -0
- package/dist/server/templates/claude-codex-advanced.json +377 -0
- package/dist/server/templates/claude-opus.json +29 -25
- package/dist/server/templates/simple-codex.json +29 -25
- package/dist/server/test-setup-node.d.ts +1 -0
- package/dist/server/test-setup-node.js +8 -0
- package/dist/server/test-setup-node.js.map +1 -0
- package/dist/server/test-setup.js +2 -0
- package/dist/server/test-setup.js.map +1 -1
- package/dist/server/tsconfig.tsbuildinfo +1 -1
- package/dist/server/ui/assets/index-C9GXCjnF.js +700 -0
- package/dist/server/ui/assets/index-o0FbZg-1.css +32 -0
- package/dist/server/ui/index.html +2 -2
- package/dist/templates/claude-codex-advanced.json +377 -0
- package/dist/templates/claude-opus.json +29 -25
- package/dist/templates/simple-codex.json +29 -25
- package/package.json +58 -27
- package/dist/server/templates/codex-claude.json +0 -178
- package/dist/server/ui/assets/index-5Xb7jFMJ.js +0 -641
- package/dist/server/ui/assets/index-CbYIbCQV.css +0 -32
- package/dist/templates/codex-claude.json +0 -178
|
@@ -20,13 +20,26 @@ const sessions_service_1 = require("../../sessions/services/sessions.service");
|
|
|
20
20
|
const terminal_gateway_1 = require("../../terminal/gateways/terminal.gateway");
|
|
21
21
|
const epics_service_1 = require("../../epics/services/epics.service");
|
|
22
22
|
const logger_1 = require("../../../common/logging/logger");
|
|
23
|
-
const path_1 = require("path");
|
|
24
23
|
const mcp_dto_1 = require("../dtos/mcp.dto");
|
|
25
24
|
const instructions_resolver_1 = require("./instructions-resolver");
|
|
26
25
|
const error_types_1 = require("../../../common/errors/error-types");
|
|
27
26
|
const common_2 = require("@nestjs/common");
|
|
28
27
|
const zod_1 = require("zod");
|
|
29
28
|
const logger = (0, logger_1.createLogger)('McpService');
|
|
29
|
+
function redactSessionId(sessionId) {
|
|
30
|
+
if (!sessionId)
|
|
31
|
+
return '(none)';
|
|
32
|
+
return sessionId.slice(0, 4) + '****';
|
|
33
|
+
}
|
|
34
|
+
function redactParams(params) {
|
|
35
|
+
if (!params || typeof params !== 'object')
|
|
36
|
+
return params;
|
|
37
|
+
const obj = params;
|
|
38
|
+
if ('sessionId' in obj && typeof obj.sessionId === 'string') {
|
|
39
|
+
return { ...obj, sessionId: redactSessionId(obj.sessionId) };
|
|
40
|
+
}
|
|
41
|
+
return params;
|
|
42
|
+
}
|
|
30
43
|
let McpService = class McpService {
|
|
31
44
|
constructor(storage, chatService, sessionsService, terminalGateway, epicsService) {
|
|
32
45
|
this.storage = storage;
|
|
@@ -65,52 +78,10 @@ let McpService = class McpService {
|
|
|
65
78
|
}
|
|
66
79
|
return { id: matches[0].id, name: matches[0].name };
|
|
67
80
|
}
|
|
68
|
-
validateAbsolutePath(pathValue) {
|
|
69
|
-
if (!pathValue) {
|
|
70
|
-
return {
|
|
71
|
-
success: false,
|
|
72
|
-
error: {
|
|
73
|
-
code: 'INVALID_PATH',
|
|
74
|
-
message: 'Path parameter is required and cannot be empty.',
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
if (!(0, path_1.isAbsolute)(pathValue)) {
|
|
79
|
-
return {
|
|
80
|
-
success: false,
|
|
81
|
-
error: {
|
|
82
|
-
code: 'INVALID_PATH',
|
|
83
|
-
message: 'Path must be an absolute filesystem path. Please provide the full project root path (e.g., /home/user/project).',
|
|
84
|
-
},
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
async resolveProject(pathValue) {
|
|
90
|
-
const pathValidation = this.validateAbsolutePath(pathValue);
|
|
91
|
-
if (pathValidation) {
|
|
92
|
-
return { error: pathValidation };
|
|
93
|
-
}
|
|
94
|
-
const project = await this.storage.findProjectByPath(pathValue);
|
|
95
|
-
if (!project) {
|
|
96
|
-
logger.debug('No project found for provided path');
|
|
97
|
-
return {
|
|
98
|
-
error: {
|
|
99
|
-
success: false,
|
|
100
|
-
error: {
|
|
101
|
-
code: 'PROJECT_NOT_FOUND',
|
|
102
|
-
message: 'No project found for provided path',
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
logger.debug({ projectId: project.id }, 'Resolved path to project');
|
|
108
|
-
return { project };
|
|
109
|
-
}
|
|
110
81
|
async handleToolCall(tool, params) {
|
|
111
82
|
try {
|
|
112
83
|
const normalizedTool = tool.replace(/[.\-/]/g, '_');
|
|
113
|
-
logger.info({ tool: normalizedTool, originalTool: tool, params }, 'Handling MCP tool call');
|
|
84
|
+
logger.info({ tool: normalizedTool, originalTool: tool, params: redactParams(params) }, 'Handling MCP tool call');
|
|
114
85
|
switch (normalizedTool) {
|
|
115
86
|
case 'devchain_create_record':
|
|
116
87
|
return await this.createRecord(params);
|
|
@@ -168,6 +139,8 @@ let McpService = class McpService {
|
|
|
168
139
|
return await this.activityStart(params);
|
|
169
140
|
case 'devchain_activity_finish':
|
|
170
141
|
return await this.activityFinish(params);
|
|
142
|
+
case 'devchain_list_sessions':
|
|
143
|
+
return await this.listSessions();
|
|
171
144
|
default:
|
|
172
145
|
logger.warn({ tool: normalizedTool }, 'Unknown MCP tool');
|
|
173
146
|
return {
|
|
@@ -336,24 +309,20 @@ let McpService = class McpService {
|
|
|
336
309
|
}
|
|
337
310
|
async listDocuments(params) {
|
|
338
311
|
const validated = mcp_dto_1.ListDocumentsParamsSchema.parse(params);
|
|
339
|
-
const
|
|
340
|
-
if (
|
|
341
|
-
return
|
|
342
|
-
}
|
|
343
|
-
const project = await this.storage.findProjectByPath(validated.path);
|
|
312
|
+
const ctx = await this.resolveSessionContext(validated.sessionId);
|
|
313
|
+
if (!ctx.success)
|
|
314
|
+
return ctx;
|
|
315
|
+
const { project } = ctx.data;
|
|
344
316
|
if (!project) {
|
|
345
|
-
logger.debug({ path: validated.path }, 'No project found for path, returning empty results');
|
|
346
317
|
return {
|
|
347
|
-
success:
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
limit: validated.limit ?? 100,
|
|
352
|
-
offset: validated.offset ?? 0,
|
|
318
|
+
success: false,
|
|
319
|
+
error: {
|
|
320
|
+
code: 'PROJECT_NOT_FOUND',
|
|
321
|
+
message: 'No project associated with this session',
|
|
353
322
|
},
|
|
354
323
|
};
|
|
355
324
|
}
|
|
356
|
-
logger.debug({
|
|
325
|
+
logger.debug({ sessionId: redactSessionId(validated.sessionId), projectId: project.id }, 'Resolved session to project');
|
|
357
326
|
const filters = {
|
|
358
327
|
projectId: project.id,
|
|
359
328
|
};
|
|
@@ -407,21 +376,20 @@ let McpService = class McpService {
|
|
|
407
376
|
}
|
|
408
377
|
async createDocument(params) {
|
|
409
378
|
const validated = mcp_dto_1.CreateDocumentParamsSchema.parse(params);
|
|
410
|
-
const
|
|
411
|
-
if (
|
|
412
|
-
return
|
|
413
|
-
}
|
|
414
|
-
const project = await this.storage.findProjectByPath(validated.path);
|
|
379
|
+
const ctx = await this.resolveSessionContext(validated.sessionId);
|
|
380
|
+
if (!ctx.success)
|
|
381
|
+
return ctx;
|
|
382
|
+
const { project } = ctx.data;
|
|
415
383
|
if (!project) {
|
|
416
384
|
return {
|
|
417
385
|
success: false,
|
|
418
386
|
error: {
|
|
419
387
|
code: 'PROJECT_NOT_FOUND',
|
|
420
|
-
message:
|
|
388
|
+
message: 'No project associated with this session',
|
|
421
389
|
},
|
|
422
390
|
};
|
|
423
391
|
}
|
|
424
|
-
logger.debug({
|
|
392
|
+
logger.debug({ sessionId: redactSessionId(validated.sessionId), projectId: project.id }, 'Resolved session to project for document creation');
|
|
425
393
|
const document = await this.storage.createDocument({
|
|
426
394
|
projectId: project.id,
|
|
427
395
|
title: validated.title,
|
|
@@ -450,39 +418,28 @@ let McpService = class McpService {
|
|
|
450
418
|
}
|
|
451
419
|
async listPrompts(params) {
|
|
452
420
|
const validated = mcp_dto_1.ListPromptsParamsSchema.parse(params);
|
|
453
|
-
|
|
454
|
-
if (
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
}
|
|
459
|
-
const project = await this.storage.findProjectByPath(validated.path);
|
|
460
|
-
if (!project) {
|
|
461
|
-
logger.debug({ path: validated.path }, 'No project found for path; returning empty prompts');
|
|
462
|
-
return { success: true, data: { prompts: [], total: 0 } };
|
|
463
|
-
}
|
|
464
|
-
projectId = project.id;
|
|
465
|
-
}
|
|
466
|
-
else if (validated.projectId) {
|
|
467
|
-
projectId = validated.projectId;
|
|
468
|
-
logger.warn('ListPrompts called with deprecated projectId parameter; prefer path');
|
|
469
|
-
}
|
|
470
|
-
else {
|
|
421
|
+
const ctx = await this.resolveSessionContext(validated.sessionId);
|
|
422
|
+
if (!ctx.success)
|
|
423
|
+
return ctx;
|
|
424
|
+
const { project } = ctx.data;
|
|
425
|
+
if (!project) {
|
|
471
426
|
return {
|
|
472
427
|
success: false,
|
|
473
|
-
error: {
|
|
428
|
+
error: {
|
|
429
|
+
code: 'PROJECT_NOT_FOUND',
|
|
430
|
+
message: 'No project associated with this session',
|
|
431
|
+
},
|
|
474
432
|
};
|
|
475
433
|
}
|
|
476
|
-
const
|
|
434
|
+
const projectId = project.id;
|
|
435
|
+
const result = await this.storage.listPrompts({
|
|
436
|
+
projectId: projectId ?? null,
|
|
437
|
+
q: validated.q,
|
|
438
|
+
});
|
|
477
439
|
let items = result.items;
|
|
478
440
|
if (validated.tags?.length) {
|
|
479
441
|
items = items.filter((prompt) => validated.tags.every((tag) => prompt.tags.includes(tag)));
|
|
480
442
|
}
|
|
481
|
-
if (validated.q) {
|
|
482
|
-
const query = validated.q.toLowerCase();
|
|
483
|
-
items = items.filter((prompt) => prompt.title.toLowerCase().includes(query) ||
|
|
484
|
-
prompt.content.toLowerCase().includes(query));
|
|
485
|
-
}
|
|
486
443
|
const response = {
|
|
487
444
|
prompts: items.map((prompt) => this.mapPromptSummary(prompt)),
|
|
488
445
|
total: items.length,
|
|
@@ -496,35 +453,28 @@ let McpService = class McpService {
|
|
|
496
453
|
prompt = await this.storage.getPrompt(validated.id);
|
|
497
454
|
}
|
|
498
455
|
else if (validated.name) {
|
|
499
|
-
|
|
500
|
-
if (validated.path) {
|
|
501
|
-
const pathCheck = this.validateAbsolutePath(validated.path);
|
|
502
|
-
if (pathCheck)
|
|
503
|
-
return pathCheck;
|
|
504
|
-
const project = await this.storage.findProjectByPath(validated.path);
|
|
505
|
-
if (!project) {
|
|
506
|
-
return {
|
|
507
|
-
success: false,
|
|
508
|
-
error: { code: 'PROJECT_NOT_FOUND', message: 'No project found for provided path' },
|
|
509
|
-
};
|
|
510
|
-
}
|
|
511
|
-
projectId = project.id;
|
|
512
|
-
}
|
|
513
|
-
else if (validated.projectId) {
|
|
514
|
-
projectId = validated.projectId;
|
|
515
|
-
logger.warn('GetPrompt called with deprecated projectId parameter; prefer path');
|
|
516
|
-
}
|
|
517
|
-
else {
|
|
456
|
+
if (!validated.sessionId) {
|
|
518
457
|
return {
|
|
519
458
|
success: false,
|
|
520
459
|
error: {
|
|
521
460
|
code: 'INVALID_PARAMS',
|
|
522
|
-
message: '
|
|
461
|
+
message: 'sessionId is required when querying prompt by name',
|
|
523
462
|
},
|
|
524
463
|
};
|
|
525
464
|
}
|
|
526
|
-
const
|
|
527
|
-
|
|
465
|
+
const ctx = await this.resolveSessionContext(validated.sessionId);
|
|
466
|
+
if (!ctx.success)
|
|
467
|
+
return ctx;
|
|
468
|
+
const { project } = ctx.data;
|
|
469
|
+
if (!project) {
|
|
470
|
+
return {
|
|
471
|
+
success: false,
|
|
472
|
+
error: { code: 'PROJECT_NOT_FOUND', message: 'No project associated with this session' },
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
const projectId = project.id;
|
|
476
|
+
const list = await this.storage.listPrompts({ projectId: projectId ?? null });
|
|
477
|
+
const found = list.items.find((item) => {
|
|
528
478
|
if (item.title !== validated.name) {
|
|
529
479
|
return false;
|
|
530
480
|
}
|
|
@@ -533,34 +483,38 @@ let McpService = class McpService {
|
|
|
533
483
|
}
|
|
534
484
|
return true;
|
|
535
485
|
});
|
|
536
|
-
if (
|
|
537
|
-
prompt = await this.storage.getPrompt(
|
|
486
|
+
if (found) {
|
|
487
|
+
prompt = await this.storage.getPrompt(found.id);
|
|
538
488
|
}
|
|
539
489
|
}
|
|
540
490
|
if (!prompt) {
|
|
541
|
-
|
|
491
|
+
return {
|
|
492
|
+
success: false,
|
|
493
|
+
error: {
|
|
494
|
+
code: 'PROMPT_NOT_FOUND',
|
|
495
|
+
message: validated.id
|
|
496
|
+
? `Prompt with id "${validated.id}" not found`
|
|
497
|
+
: `Prompt "${validated.name}"${validated.version ? ` version ${validated.version}` : ''} not found`,
|
|
498
|
+
},
|
|
499
|
+
};
|
|
542
500
|
}
|
|
543
501
|
const response = {
|
|
544
|
-
prompt: this.
|
|
502
|
+
prompt: this.mapPromptDetail(prompt),
|
|
545
503
|
};
|
|
546
504
|
return { success: true, data: response };
|
|
547
505
|
}
|
|
548
506
|
async listAgents(params) {
|
|
549
507
|
const validated = mcp_dto_1.ListAgentsParamsSchema.parse(params);
|
|
550
|
-
const
|
|
551
|
-
if (
|
|
552
|
-
return
|
|
553
|
-
}
|
|
554
|
-
const project = await this.storage.findProjectByPath(validated.path);
|
|
508
|
+
const ctx = await this.resolveSessionContext(validated.sessionId);
|
|
509
|
+
if (!ctx.success)
|
|
510
|
+
return ctx;
|
|
511
|
+
const { project } = ctx.data;
|
|
555
512
|
if (!project) {
|
|
556
|
-
logger.debug({ path: validated.path }, 'No project found for path, returning empty agent list');
|
|
557
513
|
return {
|
|
558
|
-
success:
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
limit: validated.limit ?? 100,
|
|
563
|
-
offset: validated.offset ?? 0,
|
|
514
|
+
success: false,
|
|
515
|
+
error: {
|
|
516
|
+
code: 'PROJECT_NOT_FOUND',
|
|
517
|
+
message: 'No project associated with this session',
|
|
564
518
|
},
|
|
565
519
|
};
|
|
566
520
|
}
|
|
@@ -589,17 +543,16 @@ let McpService = class McpService {
|
|
|
589
543
|
}
|
|
590
544
|
async getAgentByName(params) {
|
|
591
545
|
const validated = mcp_dto_1.GetAgentByNameParamsSchema.parse(params);
|
|
592
|
-
const
|
|
593
|
-
if (
|
|
594
|
-
return
|
|
595
|
-
}
|
|
596
|
-
const project = await this.storage.findProjectByPath(validated.path);
|
|
546
|
+
const ctx = await this.resolveSessionContext(validated.sessionId);
|
|
547
|
+
if (!ctx.success)
|
|
548
|
+
return ctx;
|
|
549
|
+
const { project } = ctx.data;
|
|
597
550
|
if (!project) {
|
|
598
551
|
return {
|
|
599
552
|
success: false,
|
|
600
553
|
error: {
|
|
601
554
|
code: 'PROJECT_NOT_FOUND',
|
|
602
|
-
message:
|
|
555
|
+
message: 'No project associated with this session',
|
|
603
556
|
},
|
|
604
557
|
};
|
|
605
558
|
}
|
|
@@ -666,11 +619,20 @@ let McpService = class McpService {
|
|
|
666
619
|
}
|
|
667
620
|
async listStatuses(params) {
|
|
668
621
|
const validated = mcp_dto_1.ListStatusesParamsSchema.parse(params);
|
|
669
|
-
const
|
|
670
|
-
if (!
|
|
671
|
-
return
|
|
622
|
+
const ctx = await this.resolveSessionContext(validated.sessionId);
|
|
623
|
+
if (!ctx.success)
|
|
624
|
+
return ctx;
|
|
625
|
+
const { project } = ctx.data;
|
|
626
|
+
if (!project) {
|
|
627
|
+
return {
|
|
628
|
+
success: false,
|
|
629
|
+
error: {
|
|
630
|
+
code: 'PROJECT_NOT_FOUND',
|
|
631
|
+
message: 'No project associated with this session',
|
|
632
|
+
},
|
|
633
|
+
};
|
|
672
634
|
}
|
|
673
|
-
const result = await this.storage.listStatuses(
|
|
635
|
+
const result = await this.storage.listStatuses(project.id, {
|
|
674
636
|
limit: 1000,
|
|
675
637
|
offset: 0,
|
|
676
638
|
});
|
|
@@ -681,11 +643,19 @@ let McpService = class McpService {
|
|
|
681
643
|
}
|
|
682
644
|
async listEpics(params) {
|
|
683
645
|
const validated = mcp_dto_1.ListEpicsParamsSchema.parse(params);
|
|
684
|
-
const
|
|
685
|
-
if (!
|
|
686
|
-
return
|
|
646
|
+
const ctx = await this.resolveSessionContext(validated.sessionId);
|
|
647
|
+
if (!ctx.success)
|
|
648
|
+
return ctx;
|
|
649
|
+
const { project } = ctx.data;
|
|
650
|
+
if (!project) {
|
|
651
|
+
return {
|
|
652
|
+
success: false,
|
|
653
|
+
error: {
|
|
654
|
+
code: 'PROJECT_NOT_FOUND',
|
|
655
|
+
message: 'No project associated with this session',
|
|
656
|
+
},
|
|
657
|
+
};
|
|
687
658
|
}
|
|
688
|
-
const project = resolution.project;
|
|
689
659
|
let statusId;
|
|
690
660
|
if (validated.statusName) {
|
|
691
661
|
const status = await this.storage.findStatusByName(project.id, validated.statusName);
|
|
@@ -708,6 +678,8 @@ let McpService = class McpService {
|
|
|
708
678
|
q: query && query.length ? query : undefined,
|
|
709
679
|
limit,
|
|
710
680
|
offset,
|
|
681
|
+
excludeMcpHidden: true,
|
|
682
|
+
parentOnly: true,
|
|
711
683
|
});
|
|
712
684
|
const statusesResult = await this.storage.listStatuses(project.id, {
|
|
713
685
|
limit: 1000,
|
|
@@ -731,12 +703,27 @@ let McpService = class McpService {
|
|
|
731
703
|
logger.warn({ agentId }, 'Failed to resolve agent name');
|
|
732
704
|
}
|
|
733
705
|
}
|
|
706
|
+
const parentIds = result.items.map((epic) => epic.id);
|
|
707
|
+
const subEpicsMap = await this.storage.listSubEpicsForParents(project.id, parentIds, {
|
|
708
|
+
excludeMcpHidden: true,
|
|
709
|
+
type: 'active',
|
|
710
|
+
limitPerParent: 50,
|
|
711
|
+
});
|
|
734
712
|
const epicsWithStatus = result.items.map((epic) => {
|
|
735
713
|
const summary = this.mapEpicSummary(epic, agentNameById);
|
|
736
714
|
const s = statusById.get(epic.statusId);
|
|
737
715
|
if (s) {
|
|
738
716
|
summary.status = this.mapStatusSummary(s);
|
|
739
717
|
}
|
|
718
|
+
const subEpics = subEpicsMap.get(epic.id) ?? [];
|
|
719
|
+
summary.subEpics = subEpics.map((subEpic) => {
|
|
720
|
+
const child = this.mapEpicChild(subEpic);
|
|
721
|
+
const subStatus = statusById.get(subEpic.statusId);
|
|
722
|
+
if (subStatus) {
|
|
723
|
+
child.status = this.mapStatusSummary(subStatus);
|
|
724
|
+
}
|
|
725
|
+
return child;
|
|
726
|
+
});
|
|
740
727
|
return summary;
|
|
741
728
|
});
|
|
742
729
|
const response = {
|
|
@@ -749,11 +736,19 @@ let McpService = class McpService {
|
|
|
749
736
|
}
|
|
750
737
|
async listAssignedEpicsTasks(params) {
|
|
751
738
|
const validated = mcp_dto_1.ListAssignedEpicsTasksParamsSchema.parse(params);
|
|
752
|
-
const
|
|
753
|
-
if (!
|
|
754
|
-
return
|
|
739
|
+
const ctx = await this.resolveSessionContext(validated.sessionId);
|
|
740
|
+
if (!ctx.success)
|
|
741
|
+
return ctx;
|
|
742
|
+
const { project } = ctx.data;
|
|
743
|
+
if (!project) {
|
|
744
|
+
return {
|
|
745
|
+
success: false,
|
|
746
|
+
error: {
|
|
747
|
+
code: 'PROJECT_NOT_FOUND',
|
|
748
|
+
message: 'No project associated with this session',
|
|
749
|
+
},
|
|
750
|
+
};
|
|
755
751
|
}
|
|
756
|
-
const project = resolution.project;
|
|
757
752
|
const limit = validated.limit ?? 100;
|
|
758
753
|
const offset = validated.offset ?? 0;
|
|
759
754
|
try {
|
|
@@ -761,6 +756,7 @@ let McpService = class McpService {
|
|
|
761
756
|
agentName: validated.agentName,
|
|
762
757
|
limit,
|
|
763
758
|
offset,
|
|
759
|
+
excludeMcpHidden: true,
|
|
764
760
|
});
|
|
765
761
|
const statusesResult = await this.storage.listStatuses(project.id, {
|
|
766
762
|
limit: 1000,
|
|
@@ -834,11 +830,19 @@ let McpService = class McpService {
|
|
|
834
830
|
};
|
|
835
831
|
}
|
|
836
832
|
const validated = mcp_dto_1.CreateEpicParamsSchema.parse(params);
|
|
837
|
-
const
|
|
838
|
-
if (!
|
|
839
|
-
return
|
|
833
|
+
const ctx = await this.resolveSessionContext(validated.sessionId);
|
|
834
|
+
if (!ctx.success)
|
|
835
|
+
return ctx;
|
|
836
|
+
const { project } = ctx.data;
|
|
837
|
+
if (!project) {
|
|
838
|
+
return {
|
|
839
|
+
success: false,
|
|
840
|
+
error: {
|
|
841
|
+
code: 'PROJECT_NOT_FOUND',
|
|
842
|
+
message: 'No project associated with this session',
|
|
843
|
+
},
|
|
844
|
+
};
|
|
840
845
|
}
|
|
841
|
-
const project = resolution.project;
|
|
842
846
|
let statusId;
|
|
843
847
|
if (validated.statusName) {
|
|
844
848
|
const status = await this.storage.findStatusByName(project.id, validated.statusName);
|
|
@@ -910,11 +914,19 @@ let McpService = class McpService {
|
|
|
910
914
|
}
|
|
911
915
|
async getEpicById(params) {
|
|
912
916
|
const validated = mcp_dto_1.GetEpicByIdParamsSchema.parse(params);
|
|
913
|
-
const
|
|
914
|
-
if (!
|
|
915
|
-
return
|
|
917
|
+
const ctx = await this.resolveSessionContext(validated.sessionId);
|
|
918
|
+
if (!ctx.success)
|
|
919
|
+
return ctx;
|
|
920
|
+
const { project } = ctx.data;
|
|
921
|
+
if (!project) {
|
|
922
|
+
return {
|
|
923
|
+
success: false,
|
|
924
|
+
error: {
|
|
925
|
+
code: 'PROJECT_NOT_FOUND',
|
|
926
|
+
message: 'No project associated with this session',
|
|
927
|
+
},
|
|
928
|
+
};
|
|
916
929
|
}
|
|
917
|
-
const project = resolution.project;
|
|
918
930
|
let epic;
|
|
919
931
|
try {
|
|
920
932
|
epic = await this.storage.getEpic(validated.id);
|
|
@@ -1013,11 +1025,28 @@ let McpService = class McpService {
|
|
|
1013
1025
|
}
|
|
1014
1026
|
async addEpicComment(params) {
|
|
1015
1027
|
const validated = mcp_dto_1.AddEpicCommentParamsSchema.parse(params);
|
|
1016
|
-
const
|
|
1017
|
-
if (!
|
|
1018
|
-
return
|
|
1028
|
+
const ctx = await this.resolveSessionContext(validated.sessionId);
|
|
1029
|
+
if (!ctx.success)
|
|
1030
|
+
return ctx;
|
|
1031
|
+
const { agent: authorAgent, project } = ctx.data;
|
|
1032
|
+
if (!authorAgent) {
|
|
1033
|
+
return {
|
|
1034
|
+
success: false,
|
|
1035
|
+
error: {
|
|
1036
|
+
code: 'AGENT_REQUIRED',
|
|
1037
|
+
message: 'Session must be associated with an agent to add comments',
|
|
1038
|
+
},
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
if (!project) {
|
|
1042
|
+
return {
|
|
1043
|
+
success: false,
|
|
1044
|
+
error: {
|
|
1045
|
+
code: 'PROJECT_NOT_FOUND',
|
|
1046
|
+
message: 'No project associated with this session',
|
|
1047
|
+
},
|
|
1048
|
+
};
|
|
1019
1049
|
}
|
|
1020
|
-
const project = resolution.project;
|
|
1021
1050
|
let epic;
|
|
1022
1051
|
try {
|
|
1023
1052
|
epic = await this.storage.getEpic(validated.epicId);
|
|
@@ -1045,7 +1074,7 @@ let McpService = class McpService {
|
|
|
1045
1074
|
}
|
|
1046
1075
|
const comment = await this.storage.createEpicComment({
|
|
1047
1076
|
epicId: validated.epicId,
|
|
1048
|
-
authorName:
|
|
1077
|
+
authorName: authorAgent.name,
|
|
1049
1078
|
content: validated.content,
|
|
1050
1079
|
});
|
|
1051
1080
|
const response = {
|
|
@@ -1075,11 +1104,19 @@ let McpService = class McpService {
|
|
|
1075
1104
|
}
|
|
1076
1105
|
}
|
|
1077
1106
|
const validated = mcp_dto_1.UpdateEpicParamsSchema.parse(preprocessedParams);
|
|
1078
|
-
const
|
|
1079
|
-
if (!
|
|
1080
|
-
return
|
|
1107
|
+
const ctx = await this.resolveSessionContext(validated.sessionId);
|
|
1108
|
+
if (!ctx.success)
|
|
1109
|
+
return ctx;
|
|
1110
|
+
const { project } = ctx.data;
|
|
1111
|
+
if (!project) {
|
|
1112
|
+
return {
|
|
1113
|
+
success: false,
|
|
1114
|
+
error: {
|
|
1115
|
+
code: 'PROJECT_NOT_FOUND',
|
|
1116
|
+
message: 'No project associated with this session',
|
|
1117
|
+
},
|
|
1118
|
+
};
|
|
1081
1119
|
}
|
|
1082
|
-
const project = resolution.project;
|
|
1083
1120
|
let epic;
|
|
1084
1121
|
try {
|
|
1085
1122
|
epic = await this.storage.getEpic(validated.id);
|
|
@@ -1261,7 +1298,7 @@ let McpService = class McpService {
|
|
|
1261
1298
|
return { success: true, data: response };
|
|
1262
1299
|
}
|
|
1263
1300
|
async sendMessage(params) {
|
|
1264
|
-
if (!this.
|
|
1301
|
+
if (!this.sessionsService) {
|
|
1265
1302
|
return {
|
|
1266
1303
|
success: false,
|
|
1267
1304
|
error: {
|
|
@@ -1271,23 +1308,33 @@ let McpService = class McpService {
|
|
|
1271
1308
|
};
|
|
1272
1309
|
}
|
|
1273
1310
|
const validated = mcp_dto_1.SendMessageParamsSchema.parse(params);
|
|
1274
|
-
const
|
|
1275
|
-
if (!
|
|
1276
|
-
return
|
|
1311
|
+
const ctx = await this.resolveSessionContext(validated.sessionId);
|
|
1312
|
+
if (!ctx.success)
|
|
1313
|
+
return ctx;
|
|
1314
|
+
const { agent: senderAgent, project } = ctx.data;
|
|
1315
|
+
if (!senderAgent) {
|
|
1316
|
+
return {
|
|
1317
|
+
success: false,
|
|
1318
|
+
error: {
|
|
1319
|
+
code: 'AGENT_REQUIRED',
|
|
1320
|
+
message: 'Session must be associated with an agent to send messages',
|
|
1321
|
+
},
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
if (!project) {
|
|
1325
|
+
return {
|
|
1326
|
+
success: false,
|
|
1327
|
+
error: {
|
|
1328
|
+
code: 'PROJECT_NOT_FOUND',
|
|
1329
|
+
message: 'No project associated with this session',
|
|
1330
|
+
},
|
|
1331
|
+
};
|
|
1277
1332
|
}
|
|
1278
|
-
const project = resolution.project;
|
|
1279
1333
|
try {
|
|
1280
|
-
|
|
1281
|
-
const
|
|
1282
|
-
const
|
|
1283
|
-
const
|
|
1284
|
-
if (senderNameInput) {
|
|
1285
|
-
const resolved = await this.resolveAgentByNameUnique(project.id, senderNameInput);
|
|
1286
|
-
if (!('id' in resolved)) {
|
|
1287
|
-
return resolved;
|
|
1288
|
-
}
|
|
1289
|
-
senderAgentId = resolved.id;
|
|
1290
|
-
}
|
|
1334
|
+
const autoLaunchSessions = process.env.NODE_ENV !== 'test';
|
|
1335
|
+
const senderAgentId = senderAgent.id;
|
|
1336
|
+
const senderAgentName = senderAgent.name;
|
|
1337
|
+
const recipientType = validated.recipient ?? 'agents';
|
|
1291
1338
|
let normalizedRecipientIds = [];
|
|
1292
1339
|
if (validated.recipientAgentNames && validated.recipientAgentNames.length > 0) {
|
|
1293
1340
|
for (const name of validated.recipientAgentNames) {
|
|
@@ -1296,6 +1343,60 @@ let McpService = class McpService {
|
|
|
1296
1343
|
}
|
|
1297
1344
|
}
|
|
1298
1345
|
normalizedRecipientIds = Array.from(new Set(normalizedRecipientIds.filter((id) => id && id !== senderAgentId)));
|
|
1346
|
+
if (!validated.threadId && senderAgentId && recipientType !== 'user') {
|
|
1347
|
+
if (normalizedRecipientIds.length === 0) {
|
|
1348
|
+
return {
|
|
1349
|
+
success: false,
|
|
1350
|
+
error: {
|
|
1351
|
+
code: 'RECIPIENTS_REQUIRED',
|
|
1352
|
+
message: 'Recipient agents must be provided when sending agent-to-agent without threadId.',
|
|
1353
|
+
},
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
const activeSessions = await this.sessionsService.listActiveSessions();
|
|
1357
|
+
const delivered = [];
|
|
1358
|
+
for (const agentId of normalizedRecipientIds) {
|
|
1359
|
+
const agent = await this.storage.getAgent(agentId);
|
|
1360
|
+
let session = activeSessions.find((s) => s.agentId === agentId);
|
|
1361
|
+
if (!session && autoLaunchSessions) {
|
|
1362
|
+
try {
|
|
1363
|
+
const launched = await this.sessionsService.launchSession({
|
|
1364
|
+
projectId: project.id,
|
|
1365
|
+
agentId,
|
|
1366
|
+
});
|
|
1367
|
+
session = launched;
|
|
1368
|
+
activeSessions.push(launched);
|
|
1369
|
+
}
|
|
1370
|
+
catch {
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
if (!session) {
|
|
1374
|
+
delivered.push({ agentName: agent.name, status: 'queued' });
|
|
1375
|
+
continue;
|
|
1376
|
+
}
|
|
1377
|
+
const injectionText = `\n[This message is sent from "${senderAgentName}" agent use devchain_send_message tool for communication]\n${validated.message}\n`;
|
|
1378
|
+
await this.sessionsService.injectTextIntoSession(session.id, injectionText);
|
|
1379
|
+
delivered.push({
|
|
1380
|
+
agentName: agent.name,
|
|
1381
|
+
status: 'delivered',
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
const response = {
|
|
1385
|
+
mode: 'direct_injection',
|
|
1386
|
+
deliveryCount: delivered.filter((d) => d.status === 'delivered').length,
|
|
1387
|
+
delivered,
|
|
1388
|
+
};
|
|
1389
|
+
return { success: true, data: response };
|
|
1390
|
+
}
|
|
1391
|
+
if (!this.chatService) {
|
|
1392
|
+
return {
|
|
1393
|
+
success: false,
|
|
1394
|
+
error: {
|
|
1395
|
+
code: 'SERVICE_UNAVAILABLE',
|
|
1396
|
+
message: 'Chat functionality requires full app context (not available in standalone MCP mode)',
|
|
1397
|
+
},
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1299
1400
|
let threadId = validated.threadId;
|
|
1300
1401
|
if (!threadId && senderAgentId) {
|
|
1301
1402
|
if (recipientType === 'user') {
|
|
@@ -1306,23 +1407,6 @@ let McpService = class McpService {
|
|
|
1306
1407
|
threadId = direct.id;
|
|
1307
1408
|
}
|
|
1308
1409
|
else {
|
|
1309
|
-
const recips = normalizedRecipientIds;
|
|
1310
|
-
if (recips.length === 0) {
|
|
1311
|
-
return {
|
|
1312
|
-
success: false,
|
|
1313
|
-
error: {
|
|
1314
|
-
code: 'RECIPIENTS_REQUIRED',
|
|
1315
|
-
message: 'Recipient agents must be provided when creating a new thread without threadId.',
|
|
1316
|
-
},
|
|
1317
|
-
};
|
|
1318
|
-
}
|
|
1319
|
-
const groupThread = await this.chatService.createGroupThread({
|
|
1320
|
-
projectId: project.id,
|
|
1321
|
-
agentIds: Array.from(new Set([senderAgentId, ...recips])),
|
|
1322
|
-
createdByType: 'agent',
|
|
1323
|
-
createdByAgentId: senderAgentId,
|
|
1324
|
-
});
|
|
1325
|
-
threadId = groupThread.id;
|
|
1326
1410
|
}
|
|
1327
1411
|
}
|
|
1328
1412
|
if (!threadId) {
|
|
@@ -1330,14 +1414,14 @@ let McpService = class McpService {
|
|
|
1330
1414
|
success: false,
|
|
1331
1415
|
error: {
|
|
1332
1416
|
code: 'THREAD_REQUIRED',
|
|
1333
|
-
message: '
|
|
1417
|
+
message: 'Unable to determine thread for message delivery',
|
|
1334
1418
|
},
|
|
1335
1419
|
};
|
|
1336
1420
|
}
|
|
1337
1421
|
const thread = await this.chatService.getThread(threadId);
|
|
1338
1422
|
const message = await this.chatService.createMessage(threadId, {
|
|
1339
|
-
authorType:
|
|
1340
|
-
authorAgentId: senderAgentId
|
|
1423
|
+
authorType: 'agent',
|
|
1424
|
+
authorAgentId: senderAgentId,
|
|
1341
1425
|
content: validated.message,
|
|
1342
1426
|
});
|
|
1343
1427
|
let targetAgentIds = normalizedRecipientIds;
|
|
@@ -1351,30 +1435,34 @@ let McpService = class McpService {
|
|
|
1351
1435
|
const delivered = [];
|
|
1352
1436
|
for (const agentId of targetAgentIds) {
|
|
1353
1437
|
const agent = await this.storage.getAgent(agentId);
|
|
1354
|
-
|
|
1355
|
-
if (session) {
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
});
|
|
1438
|
+
let session = activeSessions.find((s) => s.agentId === agentId);
|
|
1439
|
+
if (!session && autoLaunchSessions) {
|
|
1440
|
+
try {
|
|
1441
|
+
const launched = await this.sessionsService.launchSession({
|
|
1442
|
+
projectId: project.id,
|
|
1443
|
+
agentId,
|
|
1444
|
+
});
|
|
1445
|
+
session = launched;
|
|
1446
|
+
activeSessions.push(launched);
|
|
1447
|
+
}
|
|
1448
|
+
catch {
|
|
1449
|
+
}
|
|
1367
1450
|
}
|
|
1368
|
-
|
|
1369
|
-
delivered.push({
|
|
1370
|
-
|
|
1371
|
-
agentName: agent.name,
|
|
1372
|
-
sessionId: '',
|
|
1373
|
-
status: 'queued',
|
|
1374
|
-
});
|
|
1451
|
+
if (!session) {
|
|
1452
|
+
delivered.push({ agentId, agentName: agent.name, sessionId: '', status: 'queued' });
|
|
1453
|
+
continue;
|
|
1375
1454
|
}
|
|
1455
|
+
const injectionText = `\n[CHAT] From: ${senderAgentName} • Thread: ${threadId}\n${validated.message}\n[ACK] tools/call { name: "devchain_chat_ack", arguments: { sessionId: "${session.id}", thread_id: "${threadId}", message_id: "${message.id}" } }\n`;
|
|
1456
|
+
await this.sessionsService.injectTextIntoSession(session.id, injectionText);
|
|
1457
|
+
delivered.push({
|
|
1458
|
+
agentId,
|
|
1459
|
+
agentName: agent.name,
|
|
1460
|
+
sessionId: session.id,
|
|
1461
|
+
status: 'delivered',
|
|
1462
|
+
});
|
|
1376
1463
|
}
|
|
1377
1464
|
const response = {
|
|
1465
|
+
mode: 'thread',
|
|
1378
1466
|
threadId,
|
|
1379
1467
|
messageId: message.id,
|
|
1380
1468
|
deliveryCount: delivered.filter((d) => d.status === 'delivered').length,
|
|
@@ -1383,7 +1471,7 @@ let McpService = class McpService {
|
|
|
1383
1471
|
return { success: true, data: response };
|
|
1384
1472
|
}
|
|
1385
1473
|
catch (error) {
|
|
1386
|
-
logger.error({ error, params: validated }, 'sendMessage failed');
|
|
1474
|
+
logger.error({ error, params: redactParams(validated) }, 'sendMessage failed');
|
|
1387
1475
|
return {
|
|
1388
1476
|
success: false,
|
|
1389
1477
|
error: {
|
|
@@ -1405,28 +1493,32 @@ let McpService = class McpService {
|
|
|
1405
1493
|
}
|
|
1406
1494
|
const validated = mcp_dto_1.ChatAckParamsSchema.parse(params);
|
|
1407
1495
|
const { thread_id: threadId, message_id: messageId } = validated;
|
|
1408
|
-
|
|
1496
|
+
const ctx = await this.resolveSessionContext(validated.sessionId);
|
|
1497
|
+
if (!ctx.success)
|
|
1498
|
+
return ctx;
|
|
1499
|
+
const { agent } = ctx.data;
|
|
1500
|
+
if (!agent) {
|
|
1501
|
+
return {
|
|
1502
|
+
success: false,
|
|
1503
|
+
error: {
|
|
1504
|
+
code: 'AGENT_NOT_FOUND',
|
|
1505
|
+
message: 'No agent associated with this session',
|
|
1506
|
+
},
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
const agentId = agent.id;
|
|
1409
1510
|
try {
|
|
1410
1511
|
const thread = await this.chatService.getThread(threadId);
|
|
1411
1512
|
const memberIds = thread.members ?? [];
|
|
1412
|
-
|
|
1413
|
-
for (const mid of memberIds) {
|
|
1414
|
-
const a = await this.storage.getAgent(mid);
|
|
1415
|
-
if (a.name.toLowerCase() === validated.agent_name.toLowerCase()) {
|
|
1416
|
-
resolved = a.id;
|
|
1417
|
-
break;
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
if (!resolved) {
|
|
1513
|
+
if (!memberIds.includes(agentId)) {
|
|
1421
1514
|
return {
|
|
1422
1515
|
success: false,
|
|
1423
1516
|
error: {
|
|
1424
1517
|
code: 'AGENT_NOT_IN_THREAD',
|
|
1425
|
-
message: `Agent ${
|
|
1518
|
+
message: `Agent ${agent.name} is not a member of thread ${threadId}`,
|
|
1426
1519
|
},
|
|
1427
1520
|
};
|
|
1428
1521
|
}
|
|
1429
|
-
agentId = resolved;
|
|
1430
1522
|
const now = new Date().toISOString();
|
|
1431
1523
|
await this.storage.markMessageAsRead(messageId, agentId, now);
|
|
1432
1524
|
if (this.sessionsService) {
|
|
@@ -1438,28 +1530,20 @@ let McpService = class McpService {
|
|
|
1438
1530
|
}
|
|
1439
1531
|
this.terminalGateway.broadcastEvent(`chat/${threadId}`, 'message.read', {
|
|
1440
1532
|
messageId,
|
|
1441
|
-
agentId
|
|
1533
|
+
agentId,
|
|
1442
1534
|
readAt: now,
|
|
1443
1535
|
});
|
|
1444
|
-
let agentName = validated.agent_name ?? '';
|
|
1445
|
-
if (!agentName) {
|
|
1446
|
-
try {
|
|
1447
|
-
const a = await this.storage.getAgent(agentId);
|
|
1448
|
-
agentName = a.name;
|
|
1449
|
-
}
|
|
1450
|
-
catch { }
|
|
1451
|
-
}
|
|
1452
1536
|
const response = {
|
|
1453
1537
|
threadId,
|
|
1454
1538
|
messageId,
|
|
1455
|
-
agentId
|
|
1456
|
-
agentName,
|
|
1539
|
+
agentId,
|
|
1540
|
+
agentName: agent.name,
|
|
1457
1541
|
acknowledged: true,
|
|
1458
1542
|
};
|
|
1459
1543
|
return { success: true, data: response };
|
|
1460
1544
|
}
|
|
1461
1545
|
catch (error) {
|
|
1462
|
-
logger.error({ error, params: validated }, 'chatAck failed');
|
|
1546
|
+
logger.error({ error, params: redactParams(validated) }, 'chatAck failed');
|
|
1463
1547
|
return {
|
|
1464
1548
|
success: false,
|
|
1465
1549
|
error: {
|
|
@@ -1643,14 +1727,29 @@ let McpService = class McpService {
|
|
|
1643
1727
|
};
|
|
1644
1728
|
}
|
|
1645
1729
|
const validated = mcp_dto_1.ActivityStartParamsSchema.parse(params);
|
|
1646
|
-
const
|
|
1647
|
-
if (!
|
|
1648
|
-
return
|
|
1649
|
-
const project =
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1730
|
+
const ctx = await this.resolveSessionContext(validated.sessionId);
|
|
1731
|
+
if (!ctx.success)
|
|
1732
|
+
return ctx;
|
|
1733
|
+
const { project, agent } = ctx.data;
|
|
1734
|
+
if (!project) {
|
|
1735
|
+
return {
|
|
1736
|
+
success: false,
|
|
1737
|
+
error: {
|
|
1738
|
+
code: 'PROJECT_NOT_FOUND',
|
|
1739
|
+
message: 'No project associated with this session',
|
|
1740
|
+
},
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
if (!agent) {
|
|
1744
|
+
return {
|
|
1745
|
+
success: false,
|
|
1746
|
+
error: {
|
|
1747
|
+
code: 'AGENT_NOT_FOUND',
|
|
1748
|
+
message: 'No agent associated with this session',
|
|
1749
|
+
},
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
const agentId = agent.id;
|
|
1654
1753
|
let threadId = validated.threadId;
|
|
1655
1754
|
if (threadId) {
|
|
1656
1755
|
const thread = await this.chatService.getThread(threadId);
|
|
@@ -1660,7 +1759,7 @@ let McpService = class McpService {
|
|
|
1660
1759
|
success: false,
|
|
1661
1760
|
error: {
|
|
1662
1761
|
code: 'AGENT_NOT_IN_THREAD',
|
|
1663
|
-
message: `Agent ${
|
|
1762
|
+
message: `Agent ${agent.name} is not a member of thread ${threadId}`,
|
|
1664
1763
|
},
|
|
1665
1764
|
};
|
|
1666
1765
|
}
|
|
@@ -1689,14 +1788,29 @@ let McpService = class McpService {
|
|
|
1689
1788
|
};
|
|
1690
1789
|
}
|
|
1691
1790
|
const validated = mcp_dto_1.ActivityFinishParamsSchema.parse(params);
|
|
1692
|
-
const
|
|
1693
|
-
if (!
|
|
1694
|
-
return
|
|
1695
|
-
const project =
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1791
|
+
const ctx = await this.resolveSessionContext(validated.sessionId);
|
|
1792
|
+
if (!ctx.success)
|
|
1793
|
+
return ctx;
|
|
1794
|
+
const { project, agent } = ctx.data;
|
|
1795
|
+
if (!project) {
|
|
1796
|
+
return {
|
|
1797
|
+
success: false,
|
|
1798
|
+
error: {
|
|
1799
|
+
code: 'PROJECT_NOT_FOUND',
|
|
1800
|
+
message: 'No project associated with this session',
|
|
1801
|
+
},
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
if (!agent) {
|
|
1805
|
+
return {
|
|
1806
|
+
success: false,
|
|
1807
|
+
error: {
|
|
1808
|
+
code: 'AGENT_NOT_FOUND',
|
|
1809
|
+
message: 'No agent associated with this session',
|
|
1810
|
+
},
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
const agentId = agent.id;
|
|
1700
1814
|
let threadId = validated.threadId;
|
|
1701
1815
|
if (threadId) {
|
|
1702
1816
|
const thread = await this.chatService.getThread(threadId);
|
|
@@ -1706,7 +1820,7 @@ let McpService = class McpService {
|
|
|
1706
1820
|
success: false,
|
|
1707
1821
|
error: {
|
|
1708
1822
|
code: 'AGENT_NOT_IN_THREAD',
|
|
1709
|
-
message: `Agent ${
|
|
1823
|
+
message: `Agent ${agent.name} is not a member of thread ${threadId}`,
|
|
1710
1824
|
},
|
|
1711
1825
|
};
|
|
1712
1826
|
}
|
|
@@ -1740,6 +1854,160 @@ let McpService = class McpService {
|
|
|
1740
1854
|
throw error;
|
|
1741
1855
|
}
|
|
1742
1856
|
}
|
|
1857
|
+
async listSessions() {
|
|
1858
|
+
if (!this.sessionsService) {
|
|
1859
|
+
return {
|
|
1860
|
+
success: false,
|
|
1861
|
+
error: {
|
|
1862
|
+
code: 'SERVICE_UNAVAILABLE',
|
|
1863
|
+
message: 'Session listing requires full app context (not available in standalone MCP mode)',
|
|
1864
|
+
},
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
try {
|
|
1868
|
+
const activeSessions = await this.sessionsService.listActiveSessions();
|
|
1869
|
+
const agentIds = [
|
|
1870
|
+
...new Set(activeSessions.map((s) => s.agentId).filter((id) => !!id)),
|
|
1871
|
+
];
|
|
1872
|
+
const agentResults = await Promise.all(agentIds.map((id) => this.storage
|
|
1873
|
+
.getAgent(id)
|
|
1874
|
+
.catch(() => null)));
|
|
1875
|
+
const agentMap = new Map(agentResults.filter((a) => a !== null).map((a) => [a.id, a]));
|
|
1876
|
+
const projectIds = [
|
|
1877
|
+
...new Set(Array.from(agentMap.values())
|
|
1878
|
+
.map((a) => a.projectId)
|
|
1879
|
+
.filter((id) => !!id)),
|
|
1880
|
+
];
|
|
1881
|
+
const projectResults = await Promise.all(projectIds.map((id) => this.storage.getProject(id).catch(() => null)));
|
|
1882
|
+
const projectMap = new Map(projectResults.filter((p) => p !== null).map((p) => [p.id, p]));
|
|
1883
|
+
const sessions = activeSessions.map((session) => {
|
|
1884
|
+
const agent = session.agentId ? agentMap.get(session.agentId) : undefined;
|
|
1885
|
+
const project = agent?.projectId ? projectMap.get(agent.projectId) : undefined;
|
|
1886
|
+
return {
|
|
1887
|
+
sessionIdShort: session.id.slice(0, 8),
|
|
1888
|
+
agentName: agent?.name ?? 'Unknown',
|
|
1889
|
+
projectName: agent ? (project?.name ?? 'Unknown') : '',
|
|
1890
|
+
status: session.status,
|
|
1891
|
+
startedAt: session.startedAt,
|
|
1892
|
+
};
|
|
1893
|
+
});
|
|
1894
|
+
const response = { sessions };
|
|
1895
|
+
return { success: true, data: response };
|
|
1896
|
+
}
|
|
1897
|
+
catch (error) {
|
|
1898
|
+
logger.error({ error }, 'listSessions failed');
|
|
1899
|
+
return {
|
|
1900
|
+
success: false,
|
|
1901
|
+
error: {
|
|
1902
|
+
code: 'LIST_SESSIONS_FAILED',
|
|
1903
|
+
message: error instanceof Error ? error.message : 'Failed to list sessions',
|
|
1904
|
+
},
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
async resolveSessionContext(sessionId) {
|
|
1909
|
+
if (!this.sessionsService) {
|
|
1910
|
+
return {
|
|
1911
|
+
success: false,
|
|
1912
|
+
error: {
|
|
1913
|
+
code: 'SERVICE_UNAVAILABLE',
|
|
1914
|
+
message: 'Session resolution requires full app context (not available in standalone MCP mode)',
|
|
1915
|
+
},
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
if (!sessionId || sessionId.length < 8) {
|
|
1919
|
+
return {
|
|
1920
|
+
success: false,
|
|
1921
|
+
error: {
|
|
1922
|
+
code: 'INVALID_SESSION_ID',
|
|
1923
|
+
message: 'Session ID must be at least 8 characters (full UUID or prefix)',
|
|
1924
|
+
},
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
try {
|
|
1928
|
+
const activeSessions = await this.sessionsService.listActiveSessions();
|
|
1929
|
+
let matchingSessions;
|
|
1930
|
+
if (sessionId.length === 36) {
|
|
1931
|
+
matchingSessions = activeSessions.filter((s) => s.id === sessionId);
|
|
1932
|
+
}
|
|
1933
|
+
else {
|
|
1934
|
+
matchingSessions = activeSessions.filter((s) => s.id.startsWith(sessionId));
|
|
1935
|
+
}
|
|
1936
|
+
if (matchingSessions.length === 0) {
|
|
1937
|
+
return {
|
|
1938
|
+
success: false,
|
|
1939
|
+
error: {
|
|
1940
|
+
code: 'SESSION_NOT_FOUND',
|
|
1941
|
+
message: `No active session found matching '${sessionId}'`,
|
|
1942
|
+
},
|
|
1943
|
+
};
|
|
1944
|
+
}
|
|
1945
|
+
if (matchingSessions.length > 1) {
|
|
1946
|
+
const prefixLength = 12;
|
|
1947
|
+
const matchingPrefixes = matchingSessions.map((s) => s.id.slice(0, prefixLength));
|
|
1948
|
+
return {
|
|
1949
|
+
success: false,
|
|
1950
|
+
error: {
|
|
1951
|
+
code: 'AMBIGUOUS_SESSION',
|
|
1952
|
+
message: `Multiple active sessions match prefix '${sessionId}': ${matchingPrefixes.join(', ')}. Use a longer prefix (e.g., 12+ chars) or full UUID.`,
|
|
1953
|
+
data: {
|
|
1954
|
+
matchingSessionIdPrefixes: matchingPrefixes,
|
|
1955
|
+
},
|
|
1956
|
+
},
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
const session = matchingSessions[0];
|
|
1960
|
+
let agent = null;
|
|
1961
|
+
if (session.agentId) {
|
|
1962
|
+
try {
|
|
1963
|
+
const agentEntity = await this.storage.getAgent(session.agentId);
|
|
1964
|
+
agent = {
|
|
1965
|
+
id: agentEntity.id,
|
|
1966
|
+
name: agentEntity.name,
|
|
1967
|
+
projectId: agentEntity.projectId,
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
catch {
|
|
1971
|
+
logger.warn({ sessionId: redactSessionId(session.id), agentId: session.agentId }, 'Agent not found for session');
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
let project = null;
|
|
1975
|
+
if (agent?.projectId) {
|
|
1976
|
+
try {
|
|
1977
|
+
const projectEntity = await this.storage.getProject(agent.projectId);
|
|
1978
|
+
project = {
|
|
1979
|
+
id: projectEntity.id,
|
|
1980
|
+
name: projectEntity.name,
|
|
1981
|
+
rootPath: projectEntity.rootPath,
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
catch {
|
|
1985
|
+
logger.warn({ sessionId: redactSessionId(session.id), projectId: agent.projectId }, 'Project not found for session');
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
const context = {
|
|
1989
|
+
session: {
|
|
1990
|
+
id: session.id,
|
|
1991
|
+
agentId: session.agentId,
|
|
1992
|
+
status: session.status,
|
|
1993
|
+
startedAt: session.startedAt,
|
|
1994
|
+
},
|
|
1995
|
+
agent,
|
|
1996
|
+
project,
|
|
1997
|
+
};
|
|
1998
|
+
return { success: true, data: context };
|
|
1999
|
+
}
|
|
2000
|
+
catch (error) {
|
|
2001
|
+
logger.error({ error, sessionId: redactSessionId(sessionId) }, 'resolveSessionContext failed');
|
|
2002
|
+
return {
|
|
2003
|
+
success: false,
|
|
2004
|
+
error: {
|
|
2005
|
+
code: 'SESSION_RESOLUTION_FAILED',
|
|
2006
|
+
message: error instanceof Error ? error.message : 'Failed to resolve session context',
|
|
2007
|
+
},
|
|
2008
|
+
};
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
1743
2011
|
mapStatusSummary(status) {
|
|
1744
2012
|
return {
|
|
1745
2013
|
id: status.id,
|
|
@@ -1765,6 +2033,7 @@ let McpService = class McpService {
|
|
|
1765
2033
|
if (epic.parentId) {
|
|
1766
2034
|
summary.parentId = epic.parentId;
|
|
1767
2035
|
}
|
|
2036
|
+
summary.tags = epic.tags ?? [];
|
|
1768
2037
|
return summary;
|
|
1769
2038
|
}
|
|
1770
2039
|
mapEpicChild(epic) {
|
|
@@ -1814,6 +2083,23 @@ let McpService = class McpService {
|
|
|
1814
2083
|
id: prompt.id,
|
|
1815
2084
|
projectId: prompt.projectId,
|
|
1816
2085
|
title: prompt.title,
|
|
2086
|
+
contentPreview: prompt.contentPreview,
|
|
2087
|
+
tags: prompt.tags,
|
|
2088
|
+
version: prompt.version,
|
|
2089
|
+
createdAt: prompt.createdAt,
|
|
2090
|
+
updatedAt: prompt.updatedAt,
|
|
2091
|
+
};
|
|
2092
|
+
}
|
|
2093
|
+
mapPromptDetail(prompt) {
|
|
2094
|
+
const PREVIEW_LENGTH = 200;
|
|
2095
|
+
const contentPreview = prompt.content.length > PREVIEW_LENGTH
|
|
2096
|
+
? prompt.content.slice(0, PREVIEW_LENGTH) + '…'
|
|
2097
|
+
: prompt.content;
|
|
2098
|
+
return {
|
|
2099
|
+
id: prompt.id,
|
|
2100
|
+
projectId: prompt.projectId,
|
|
2101
|
+
title: prompt.title,
|
|
2102
|
+
contentPreview,
|
|
1817
2103
|
content: prompt.content,
|
|
1818
2104
|
tags: prompt.tags,
|
|
1819
2105
|
version: prompt.version,
|
|
@@ -2004,7 +2290,7 @@ let McpService = class McpService {
|
|
|
2004
2290
|
throw new Error(`Invalid prompt version in URI: ${uri}`);
|
|
2005
2291
|
}
|
|
2006
2292
|
}
|
|
2007
|
-
const list = await this.storage.listPrompts(null
|
|
2293
|
+
const list = await this.storage.listPrompts({ projectId: null });
|
|
2008
2294
|
const candidates = list.items.filter((prompt) => prompt.title === name && (version === undefined || prompt.version === version));
|
|
2009
2295
|
const selected = candidates.find((prompt) => prompt.projectId === null) ?? candidates[0];
|
|
2010
2296
|
if (!selected) {
|
|
@@ -2023,7 +2309,7 @@ let McpService = class McpService {
|
|
|
2023
2309
|
uri,
|
|
2024
2310
|
mimeType: 'text/markdown',
|
|
2025
2311
|
content: prompt.content,
|
|
2026
|
-
prompt: this.
|
|
2312
|
+
prompt: this.mapPromptDetail(prompt),
|
|
2027
2313
|
},
|
|
2028
2314
|
};
|
|
2029
2315
|
}
|