devchain-cli 0.5.1 → 0.6.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/dist/drizzle/0022_guests.sql +16 -0
- package/dist/drizzle/0023_add_guest_description.sql +1 -0
- package/dist/drizzle/meta/0022_snapshot.json +3064 -0
- package/dist/drizzle/meta/0023_snapshot.json +3069 -0
- package/dist/drizzle/meta/_journal.json +15 -1
- package/dist/server/app.module.js +2 -0
- package/dist/server/app.module.js.map +1 -1
- package/dist/server/modules/agents/controllers/agents.controller.d.ts +14 -1
- package/dist/server/modules/agents/controllers/agents.controller.js +30 -4
- package/dist/server/modules/agents/controllers/agents.controller.js.map +1 -1
- package/dist/server/modules/events/catalog/guest.registered.d.ts +24 -0
- package/dist/server/modules/events/catalog/guest.registered.js +15 -0
- package/dist/server/modules/events/catalog/guest.registered.js.map +1 -0
- package/dist/server/modules/events/catalog/guest.unregistered.d.ts +24 -0
- package/dist/server/modules/events/catalog/guest.unregistered.js +15 -0
- package/dist/server/modules/events/catalog/guest.unregistered.js.map +1 -0
- package/dist/server/modules/events/catalog/index.d.ts +38 -0
- package/dist/server/modules/events/catalog/index.js +4 -0
- package/dist/server/modules/events/catalog/index.js.map +1 -1
- package/dist/server/modules/guests/constants.d.ts +3 -0
- package/dist/server/modules/guests/constants.js +7 -0
- package/dist/server/modules/guests/constants.js.map +1 -0
- package/dist/server/modules/guests/dtos/guest.dto.d.ts +21 -0
- package/dist/server/modules/guests/dtos/guest.dto.js +3 -0
- package/dist/server/modules/guests/dtos/guest.dto.js.map +1 -0
- package/dist/server/modules/guests/guests.module.d.ts +2 -0
- package/dist/server/modules/guests/guests.module.js +26 -0
- package/dist/server/modules/guests/guests.module.js.map +1 -0
- package/dist/server/modules/guests/services/guest-health.service.d.ts +21 -0
- package/dist/server/modules/guests/services/guest-health.service.js +125 -0
- package/dist/server/modules/guests/services/guest-health.service.js.map +1 -0
- package/dist/server/modules/guests/services/guests.service.d.ts +30 -0
- package/dist/server/modules/guests/services/guests.service.js +178 -0
- package/dist/server/modules/guests/services/guests.service.js.map +1 -0
- package/dist/server/modules/mcp/controllers/mcp-http.controller.js +22 -0
- package/dist/server/modules/mcp/controllers/mcp-http.controller.js.map +1 -1
- package/dist/server/modules/mcp/controllers/mcp-sdk.controller.js +22 -0
- package/dist/server/modules/mcp/controllers/mcp-sdk.controller.js.map +1 -1
- package/dist/server/modules/mcp/dtos/mcp.dto.d.ts +67 -5
- package/dist/server/modules/mcp/dtos/mcp.dto.js +13 -1
- package/dist/server/modules/mcp/dtos/mcp.dto.js.map +1 -1
- package/dist/server/modules/mcp/mcp.module.js +2 -0
- package/dist/server/modules/mcp/mcp.module.js.map +1 -1
- package/dist/server/modules/mcp/services/mcp.service.d.ts +9 -1
- package/dist/server/modules/mcp/services/mcp.service.js +357 -71
- package/dist/server/modules/mcp/services/mcp.service.js.map +1 -1
- package/dist/server/modules/storage/db/schema.d.ts +159 -0
- package/dist/server/modules/storage/db/schema.js +18 -1
- package/dist/server/modules/storage/db/schema.js.map +1 -1
- package/dist/server/modules/storage/interfaces/storage.interface.d.ts +12 -1
- package/dist/server/modules/storage/interfaces/storage.interface.js.map +1 -1
- package/dist/server/modules/storage/local/local-storage.service.d.ts +12 -1
- package/dist/server/modules/storage/local/local-storage.service.js +194 -1
- package/dist/server/modules/storage/local/local-storage.service.js.map +1 -1
- package/dist/server/modules/storage/models/domain.models.d.ts +12 -0
- package/dist/server/modules/terminal/services/tmux.service.d.ts +2 -0
- package/dist/server/modules/terminal/services/tmux.service.js +74 -13
- package/dist/server/modules/terminal/services/tmux.service.js.map +1 -1
- package/dist/server/tsconfig.tsbuildinfo +1 -1
- package/dist/server/ui/assets/{index-Bgulh8ua.js → index-hB0e02VB.js} +165 -165
- package/dist/server/ui/index.html +1 -1
- package/package.json +9 -2
|
@@ -19,8 +19,10 @@ const chat_service_1 = require("../../chat/services/chat.service");
|
|
|
19
19
|
const sessions_service_1 = require("../../sessions/services/sessions.service");
|
|
20
20
|
const sessions_message_pool_service_1 = require("../../sessions/services/sessions-message-pool.service");
|
|
21
21
|
const terminal_gateway_1 = require("../../terminal/gateways/terminal.gateway");
|
|
22
|
+
const tmux_service_1 = require("../../terminal/services/tmux.service");
|
|
22
23
|
const epics_service_1 = require("../../epics/services/epics.service");
|
|
23
24
|
const settings_service_1 = require("../../settings/services/settings.service");
|
|
25
|
+
const guests_service_1 = require("../../guests/services/guests.service");
|
|
24
26
|
const logger_1 = require("../../../common/logging/logger");
|
|
25
27
|
const mcp_dto_1 = require("../dtos/mcp.dto");
|
|
26
28
|
const instructions_resolver_1 = require("./instructions-resolver");
|
|
@@ -28,6 +30,19 @@ const error_types_1 = require("../../../common/errors/error-types");
|
|
|
28
30
|
const common_2 = require("@nestjs/common");
|
|
29
31
|
const zod_1 = require("zod");
|
|
30
32
|
const logger = (0, logger_1.createLogger)('McpService');
|
|
33
|
+
function getActorFromContext(ctx) {
|
|
34
|
+
if (ctx.type === 'agent') {
|
|
35
|
+
return ctx.agent;
|
|
36
|
+
}
|
|
37
|
+
else if (ctx.type === 'guest') {
|
|
38
|
+
return {
|
|
39
|
+
id: ctx.guest.id,
|
|
40
|
+
name: ctx.guest.name,
|
|
41
|
+
projectId: ctx.guest.projectId,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
31
46
|
function redactSessionId(sessionId) {
|
|
32
47
|
if (!sessionId)
|
|
33
48
|
return '(none)';
|
|
@@ -43,14 +58,16 @@ function redactParams(params) {
|
|
|
43
58
|
return params;
|
|
44
59
|
}
|
|
45
60
|
let McpService = class McpService {
|
|
46
|
-
constructor(storage, chatService, sessionsService, messagePoolService, terminalGateway, epicsService, settingsService) {
|
|
61
|
+
constructor(storage, chatService, sessionsService, messagePoolService, terminalGateway, tmuxService, epicsService, settingsService, guestsService) {
|
|
47
62
|
this.storage = storage;
|
|
48
63
|
this.chatService = chatService;
|
|
49
64
|
this.sessionsService = sessionsService;
|
|
50
65
|
this.messagePoolService = messagePoolService;
|
|
51
66
|
this.terminalGateway = terminalGateway;
|
|
67
|
+
this.tmuxService = tmuxService;
|
|
52
68
|
this.epicsService = epicsService;
|
|
53
69
|
this.settingsService = settingsService;
|
|
70
|
+
this.guestsService = guestsService;
|
|
54
71
|
this.DEFAULT_INLINE_MAX_BYTES = 64 * 1024;
|
|
55
72
|
logger.info('McpService initialized');
|
|
56
73
|
this.featureFlags = this.storage.getFeatureFlags();
|
|
@@ -145,6 +162,8 @@ let McpService = class McpService {
|
|
|
145
162
|
return await this.activityFinish(params);
|
|
146
163
|
case 'devchain_list_sessions':
|
|
147
164
|
return await this.listSessions();
|
|
165
|
+
case 'devchain_register_guest':
|
|
166
|
+
return await this.registerGuest(params);
|
|
148
167
|
default:
|
|
149
168
|
logger.warn({ tool: normalizedTool }, 'Unknown MCP tool');
|
|
150
169
|
return {
|
|
@@ -525,21 +544,49 @@ let McpService = class McpService {
|
|
|
525
544
|
const limit = validated.limit ?? 100;
|
|
526
545
|
const offset = validated.offset ?? 0;
|
|
527
546
|
const normalizedQuery = validated.q?.toLowerCase();
|
|
528
|
-
const
|
|
529
|
-
const
|
|
530
|
-
|
|
547
|
+
const MAX_COMBINED_FETCH = 1000;
|
|
548
|
+
const [agentsResult, guests] = await Promise.all([
|
|
549
|
+
this.storage.listAgents(project.id, { limit: MAX_COMBINED_FETCH, offset: 0 }),
|
|
550
|
+
this.storage.listGuests(project.id),
|
|
551
|
+
]);
|
|
552
|
+
const [agentPresence, tmuxSessions] = await Promise.all([
|
|
553
|
+
this.sessionsService
|
|
554
|
+
? this.sessionsService.getAgentPresence(project.id)
|
|
555
|
+
: Promise.resolve(new Map()),
|
|
556
|
+
this.tmuxService
|
|
557
|
+
? this.tmuxService.listAllSessionNames()
|
|
558
|
+
: Promise.resolve(new Set()),
|
|
559
|
+
]);
|
|
560
|
+
const agentItems = agentsResult.items.map((agent) => ({
|
|
561
|
+
id: agent.id,
|
|
562
|
+
name: agent.name,
|
|
563
|
+
profileId: agent.profileId,
|
|
564
|
+
description: agent.description,
|
|
565
|
+
type: 'agent',
|
|
566
|
+
online: agentPresence.get(agent.id)?.online ?? false,
|
|
567
|
+
}));
|
|
568
|
+
const guestItems = guests.map((guest) => ({
|
|
569
|
+
id: guest.id,
|
|
570
|
+
name: guest.name,
|
|
571
|
+
profileId: null,
|
|
572
|
+
description: guest.description,
|
|
573
|
+
type: 'guest',
|
|
574
|
+
online: tmuxSessions.has(guest.tmuxSessionId),
|
|
575
|
+
}));
|
|
576
|
+
let allItems = [...agentItems, ...guestItems].sort((a, b) => {
|
|
577
|
+
const nameCompare = a.name.localeCompare(b.name);
|
|
578
|
+
if (nameCompare !== 0)
|
|
579
|
+
return nameCompare;
|
|
580
|
+
return a.type === 'agent' ? -1 : 1;
|
|
581
|
+
});
|
|
531
582
|
if (normalizedQuery) {
|
|
532
|
-
|
|
583
|
+
allItems = allItems.filter((item) => item.name.toLowerCase().includes(normalizedQuery));
|
|
533
584
|
}
|
|
534
|
-
const
|
|
585
|
+
const total = allItems.length;
|
|
586
|
+
const paginatedItems = allItems.slice(offset, offset + limit);
|
|
535
587
|
const response = {
|
|
536
|
-
agents:
|
|
537
|
-
|
|
538
|
-
name: agent.name,
|
|
539
|
-
profileId: agent.profileId,
|
|
540
|
-
description: agent.description,
|
|
541
|
-
})),
|
|
542
|
-
total: normalizedQuery ? agents.length : result.total,
|
|
588
|
+
agents: paginatedItems,
|
|
589
|
+
total,
|
|
543
590
|
limit,
|
|
544
591
|
offset,
|
|
545
592
|
};
|
|
@@ -1038,16 +1085,18 @@ let McpService = class McpService {
|
|
|
1038
1085
|
const ctx = await this.resolveSessionContext(validated.sessionId);
|
|
1039
1086
|
if (!ctx.success)
|
|
1040
1087
|
return ctx;
|
|
1041
|
-
const
|
|
1042
|
-
|
|
1088
|
+
const sessionCtx = ctx.data;
|
|
1089
|
+
const authorActor = getActorFromContext(sessionCtx);
|
|
1090
|
+
if (!authorActor) {
|
|
1043
1091
|
return {
|
|
1044
1092
|
success: false,
|
|
1045
1093
|
error: {
|
|
1046
1094
|
code: 'AGENT_REQUIRED',
|
|
1047
|
-
message: 'Session must be associated with an agent to add comments',
|
|
1095
|
+
message: 'Session must be associated with an agent or guest to add comments',
|
|
1048
1096
|
},
|
|
1049
1097
|
};
|
|
1050
1098
|
}
|
|
1099
|
+
const project = sessionCtx.project;
|
|
1051
1100
|
if (!project) {
|
|
1052
1101
|
return {
|
|
1053
1102
|
success: false,
|
|
@@ -1084,7 +1133,7 @@ let McpService = class McpService {
|
|
|
1084
1133
|
}
|
|
1085
1134
|
const comment = await this.storage.createEpicComment({
|
|
1086
1135
|
epicId: validated.epicId,
|
|
1087
|
-
authorName:
|
|
1136
|
+
authorName: authorActor.name,
|
|
1088
1137
|
content: validated.content,
|
|
1089
1138
|
});
|
|
1090
1139
|
const response = {
|
|
@@ -1307,6 +1356,40 @@ let McpService = class McpService {
|
|
|
1307
1356
|
};
|
|
1308
1357
|
return { success: true, data: response };
|
|
1309
1358
|
}
|
|
1359
|
+
async resolveRecipientByName(projectId, name) {
|
|
1360
|
+
try {
|
|
1361
|
+
const agent = await this.storage.getAgentByName(projectId, name);
|
|
1362
|
+
return {
|
|
1363
|
+
type: 'agent',
|
|
1364
|
+
id: agent.id,
|
|
1365
|
+
name: agent.name,
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
catch (error) {
|
|
1369
|
+
if (!(error instanceof error_types_1.NotFoundError)) {
|
|
1370
|
+
throw error;
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
const guest = await this.storage.getGuestByName(projectId, name);
|
|
1374
|
+
if (guest) {
|
|
1375
|
+
return {
|
|
1376
|
+
type: 'guest',
|
|
1377
|
+
id: guest.id,
|
|
1378
|
+
name: guest.name,
|
|
1379
|
+
tmuxSessionId: guest.tmuxSessionId,
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
return null;
|
|
1383
|
+
}
|
|
1384
|
+
async getAvailableRecipientNames(projectId) {
|
|
1385
|
+
const [agentsResult, guests] = await Promise.all([
|
|
1386
|
+
this.storage.listAgents(projectId, { limit: 100, offset: 0 }),
|
|
1387
|
+
this.storage.listGuests(projectId),
|
|
1388
|
+
]);
|
|
1389
|
+
const agentNames = agentsResult.items.map((a) => a.name);
|
|
1390
|
+
const guestNames = guests.map((g) => `${g.name} (guest)`);
|
|
1391
|
+
return [...agentNames, ...guestNames];
|
|
1392
|
+
}
|
|
1310
1393
|
async sendMessage(params) {
|
|
1311
1394
|
if (!this.sessionsService) {
|
|
1312
1395
|
return {
|
|
@@ -1321,13 +1404,15 @@ let McpService = class McpService {
|
|
|
1321
1404
|
const ctx = await this.resolveSessionContext(validated.sessionId);
|
|
1322
1405
|
if (!ctx.success)
|
|
1323
1406
|
return ctx;
|
|
1324
|
-
const
|
|
1325
|
-
|
|
1407
|
+
const sessionCtx = ctx.data;
|
|
1408
|
+
const sender = getActorFromContext(sessionCtx);
|
|
1409
|
+
const project = sessionCtx.project;
|
|
1410
|
+
if (!sender) {
|
|
1326
1411
|
return {
|
|
1327
1412
|
success: false,
|
|
1328
1413
|
error: {
|
|
1329
1414
|
code: 'AGENT_REQUIRED',
|
|
1330
|
-
message: 'Session must be associated with an agent to send messages',
|
|
1415
|
+
message: 'Session must be associated with an agent or guest to send messages',
|
|
1331
1416
|
},
|
|
1332
1417
|
};
|
|
1333
1418
|
}
|
|
@@ -1340,20 +1425,53 @@ let McpService = class McpService {
|
|
|
1340
1425
|
},
|
|
1341
1426
|
};
|
|
1342
1427
|
}
|
|
1428
|
+
if (sessionCtx.type === 'guest') {
|
|
1429
|
+
if (validated.threadId) {
|
|
1430
|
+
return {
|
|
1431
|
+
success: false,
|
|
1432
|
+
error: {
|
|
1433
|
+
code: 'GUEST_THREAD_NOT_ALLOWED',
|
|
1434
|
+
message: 'Guests cannot use threaded messaging. Use recipientAgentNames for direct messaging.',
|
|
1435
|
+
},
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
if (validated.recipient === 'user') {
|
|
1439
|
+
return {
|
|
1440
|
+
success: false,
|
|
1441
|
+
error: {
|
|
1442
|
+
code: 'GUEST_USER_DM_NOT_ALLOWED',
|
|
1443
|
+
message: 'Guests cannot send direct messages to users.',
|
|
1444
|
+
},
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1343
1448
|
try {
|
|
1344
1449
|
const autoLaunchSessions = process.env.NODE_ENV !== 'test';
|
|
1345
|
-
const
|
|
1346
|
-
const
|
|
1450
|
+
const senderId = sender.id;
|
|
1451
|
+
const senderName = sender.name;
|
|
1452
|
+
const senderType = sessionCtx.type;
|
|
1347
1453
|
const recipientType = validated.recipient ?? 'agents';
|
|
1348
|
-
|
|
1454
|
+
const resolvedRecipients = [];
|
|
1349
1455
|
if (validated.recipientAgentNames && validated.recipientAgentNames.length > 0) {
|
|
1350
1456
|
for (const name of validated.recipientAgentNames) {
|
|
1351
|
-
const
|
|
1352
|
-
|
|
1457
|
+
const recipient = await this.resolveRecipientByName(project.id, name);
|
|
1458
|
+
if (!recipient) {
|
|
1459
|
+
const availableNames = await this.getAvailableRecipientNames(project.id);
|
|
1460
|
+
return {
|
|
1461
|
+
success: false,
|
|
1462
|
+
error: {
|
|
1463
|
+
code: 'RECIPIENT_NOT_FOUND',
|
|
1464
|
+
message: `Recipient "${name}" not found. Available: ${availableNames.join(', ') || 'none'}`,
|
|
1465
|
+
},
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
if (recipient.id !== senderId) {
|
|
1469
|
+
resolvedRecipients.push(recipient);
|
|
1470
|
+
}
|
|
1353
1471
|
}
|
|
1354
1472
|
}
|
|
1355
|
-
|
|
1356
|
-
if (!validated.threadId &&
|
|
1473
|
+
const uniqueRecipients = resolvedRecipients.filter((r, i, arr) => arr.findIndex((x) => x.id === r.id) === i);
|
|
1474
|
+
if (!validated.threadId && senderId && recipientType !== 'user') {
|
|
1357
1475
|
if (!this.messagePoolService || !this.settingsService) {
|
|
1358
1476
|
return {
|
|
1359
1477
|
success: false,
|
|
@@ -1363,12 +1481,12 @@ let McpService = class McpService {
|
|
|
1363
1481
|
},
|
|
1364
1482
|
};
|
|
1365
1483
|
}
|
|
1366
|
-
if (
|
|
1484
|
+
if (uniqueRecipients.length === 0) {
|
|
1367
1485
|
return {
|
|
1368
1486
|
success: false,
|
|
1369
1487
|
error: {
|
|
1370
1488
|
code: 'RECIPIENTS_REQUIRED',
|
|
1371
|
-
message: '
|
|
1489
|
+
message: 'Recipients must be provided when sending without threadId.',
|
|
1372
1490
|
},
|
|
1373
1491
|
};
|
|
1374
1492
|
}
|
|
@@ -1377,35 +1495,77 @@ let McpService = class McpService {
|
|
|
1377
1495
|
const activeSessions = this.sessionsService
|
|
1378
1496
|
? await this.sessionsService.listActiveSessions()
|
|
1379
1497
|
: [];
|
|
1380
|
-
for (const
|
|
1381
|
-
const
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1498
|
+
for (const recipient of uniqueRecipients) {
|
|
1499
|
+
const injectionText = `\n[This message is sent from "${senderName}" ${senderType} use devchain_send_message tool for communication]\n${validated.message}\n`;
|
|
1500
|
+
if (recipient.type === 'agent') {
|
|
1501
|
+
let session = activeSessions.find((s) => s.agentId === recipient.id);
|
|
1502
|
+
let wasLaunched = false;
|
|
1503
|
+
if (!session && autoLaunchSessions && this.sessionsService) {
|
|
1504
|
+
try {
|
|
1505
|
+
const launched = await this.sessionsService.launchSession({
|
|
1506
|
+
projectId: project.id,
|
|
1507
|
+
agentId: recipient.id,
|
|
1508
|
+
});
|
|
1509
|
+
session = launched;
|
|
1510
|
+
activeSessions.push(launched);
|
|
1511
|
+
wasLaunched = true;
|
|
1512
|
+
}
|
|
1513
|
+
catch {
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
await this.messagePoolService.enqueue(recipient.id, injectionText, {
|
|
1517
|
+
source: 'mcp.send_message',
|
|
1518
|
+
submitKeys: ['Enter'],
|
|
1519
|
+
senderAgentId: senderId,
|
|
1520
|
+
projectId: project.id,
|
|
1521
|
+
agentName: recipient.name,
|
|
1522
|
+
});
|
|
1523
|
+
queued.push({
|
|
1524
|
+
name: recipient.name,
|
|
1525
|
+
type: 'agent',
|
|
1526
|
+
status: wasLaunched ? 'launched' : 'queued',
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
else {
|
|
1530
|
+
const isOnline = this.tmuxService
|
|
1531
|
+
? await this.tmuxService.hasSession(recipient.tmuxSessionId)
|
|
1532
|
+
: false;
|
|
1533
|
+
if (!isOnline) {
|
|
1534
|
+
queued.push({
|
|
1535
|
+
name: recipient.name,
|
|
1536
|
+
type: 'guest',
|
|
1537
|
+
status: 'failed',
|
|
1538
|
+
error: 'Recipient offline',
|
|
1389
1539
|
});
|
|
1390
|
-
session = launched;
|
|
1391
|
-
activeSessions.push(launched);
|
|
1392
|
-
wasLaunched = true;
|
|
1393
1540
|
}
|
|
1394
|
-
|
|
1541
|
+
else if (this.tmuxService) {
|
|
1542
|
+
try {
|
|
1543
|
+
await this.tmuxService.pasteAndSubmit(recipient.tmuxSessionId, injectionText);
|
|
1544
|
+
queued.push({
|
|
1545
|
+
name: recipient.name,
|
|
1546
|
+
type: 'guest',
|
|
1547
|
+
status: 'delivered',
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
catch (error) {
|
|
1551
|
+
logger.warn({ guestId: recipient.id, tmuxSessionId: recipient.tmuxSessionId, error }, 'Failed to deliver message to guest');
|
|
1552
|
+
queued.push({
|
|
1553
|
+
name: recipient.name,
|
|
1554
|
+
type: 'guest',
|
|
1555
|
+
status: 'failed',
|
|
1556
|
+
error: error instanceof Error ? error.message : 'Delivery failed',
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
else {
|
|
1561
|
+
queued.push({
|
|
1562
|
+
name: recipient.name,
|
|
1563
|
+
type: 'guest',
|
|
1564
|
+
status: 'failed',
|
|
1565
|
+
error: 'Tmux service unavailable',
|
|
1566
|
+
});
|
|
1395
1567
|
}
|
|
1396
1568
|
}
|
|
1397
|
-
const injectionText = `\n[This message is sent from "${senderAgentName}" agent use devchain_send_message tool for communication]\n${validated.message}\n`;
|
|
1398
|
-
await this.messagePoolService.enqueue(agentId, injectionText, {
|
|
1399
|
-
source: 'mcp.send_message',
|
|
1400
|
-
submitKeys: ['Enter'],
|
|
1401
|
-
senderAgentId: senderAgentId,
|
|
1402
|
-
projectId: project.id,
|
|
1403
|
-
agentName: agent.name,
|
|
1404
|
-
});
|
|
1405
|
-
queued.push({
|
|
1406
|
-
agentName: agent.name,
|
|
1407
|
-
status: wasLaunched ? 'launched' : 'queued',
|
|
1408
|
-
});
|
|
1409
1569
|
}
|
|
1410
1570
|
const response = {
|
|
1411
1571
|
mode: 'pooled',
|
|
@@ -1425,11 +1585,11 @@ let McpService = class McpService {
|
|
|
1425
1585
|
};
|
|
1426
1586
|
}
|
|
1427
1587
|
let threadId = validated.threadId;
|
|
1428
|
-
if (!threadId &&
|
|
1588
|
+
if (!threadId && senderId) {
|
|
1429
1589
|
if (recipientType === 'user') {
|
|
1430
1590
|
const direct = await this.chatService.createDirectThread({
|
|
1431
1591
|
projectId: project.id,
|
|
1432
|
-
agentId:
|
|
1592
|
+
agentId: senderId,
|
|
1433
1593
|
});
|
|
1434
1594
|
threadId = direct.id;
|
|
1435
1595
|
}
|
|
@@ -1448,15 +1608,12 @@ let McpService = class McpService {
|
|
|
1448
1608
|
const thread = await this.chatService.getThread(threadId);
|
|
1449
1609
|
const message = await this.chatService.createMessage(threadId, {
|
|
1450
1610
|
authorType: 'agent',
|
|
1451
|
-
authorAgentId:
|
|
1611
|
+
authorAgentId: senderId,
|
|
1452
1612
|
content: validated.message,
|
|
1453
1613
|
});
|
|
1454
|
-
let targetAgentIds =
|
|
1455
|
-
if (
|
|
1456
|
-
thread.members
|
|
1457
|
-
thread.members.length > 1 &&
|
|
1458
|
-
targetAgentIds.length === 0) {
|
|
1459
|
-
targetAgentIds = thread.members.filter((id) => id !== senderAgentId);
|
|
1614
|
+
let targetAgentIds = uniqueRecipients.filter((r) => r.type === 'agent').map((r) => r.id);
|
|
1615
|
+
if (senderId && thread.members && thread.members.length > 1 && targetAgentIds.length === 0) {
|
|
1616
|
+
targetAgentIds = thread.members.filter((id) => id !== senderId);
|
|
1460
1617
|
}
|
|
1461
1618
|
const activeSessions = await this.sessionsService.listActiveSessions();
|
|
1462
1619
|
const delivered = [];
|
|
@@ -1479,7 +1636,7 @@ let McpService = class McpService {
|
|
|
1479
1636
|
delivered.push({ agentId, agentName: agent.name, sessionId: '', status: 'queued' });
|
|
1480
1637
|
continue;
|
|
1481
1638
|
}
|
|
1482
|
-
const injectionText = `\n[CHAT] From: ${
|
|
1639
|
+
const injectionText = `\n[CHAT] From: ${senderName} • 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`;
|
|
1483
1640
|
await this.sessionsService.injectTextIntoSession(session.id, injectionText);
|
|
1484
1641
|
delivered.push({
|
|
1485
1642
|
agentId,
|
|
@@ -1523,7 +1680,8 @@ let McpService = class McpService {
|
|
|
1523
1680
|
const ctx = await this.resolveSessionContext(validated.sessionId);
|
|
1524
1681
|
if (!ctx.success)
|
|
1525
1682
|
return ctx;
|
|
1526
|
-
const
|
|
1683
|
+
const sessionCtx = ctx.data;
|
|
1684
|
+
const agent = getActorFromContext(sessionCtx);
|
|
1527
1685
|
if (!agent) {
|
|
1528
1686
|
return {
|
|
1529
1687
|
success: false,
|
|
@@ -1757,7 +1915,18 @@ let McpService = class McpService {
|
|
|
1757
1915
|
const ctx = await this.resolveSessionContext(validated.sessionId);
|
|
1758
1916
|
if (!ctx.success)
|
|
1759
1917
|
return ctx;
|
|
1760
|
-
const
|
|
1918
|
+
const sessionCtx = ctx.data;
|
|
1919
|
+
const project = sessionCtx.project;
|
|
1920
|
+
const agent = getActorFromContext(sessionCtx);
|
|
1921
|
+
if (sessionCtx.type === 'guest') {
|
|
1922
|
+
return {
|
|
1923
|
+
success: false,
|
|
1924
|
+
error: {
|
|
1925
|
+
code: 'GUEST_ACTIVITY_NOT_ALLOWED',
|
|
1926
|
+
message: 'Guests cannot use activity tools.',
|
|
1927
|
+
},
|
|
1928
|
+
};
|
|
1929
|
+
}
|
|
1761
1930
|
if (!project) {
|
|
1762
1931
|
return {
|
|
1763
1932
|
success: false,
|
|
@@ -1818,7 +1987,18 @@ let McpService = class McpService {
|
|
|
1818
1987
|
const ctx = await this.resolveSessionContext(validated.sessionId);
|
|
1819
1988
|
if (!ctx.success)
|
|
1820
1989
|
return ctx;
|
|
1821
|
-
const
|
|
1990
|
+
const sessionCtx = ctx.data;
|
|
1991
|
+
const project = sessionCtx.project;
|
|
1992
|
+
const agent = getActorFromContext(sessionCtx);
|
|
1993
|
+
if (sessionCtx.type === 'guest') {
|
|
1994
|
+
return {
|
|
1995
|
+
success: false,
|
|
1996
|
+
error: {
|
|
1997
|
+
code: 'GUEST_ACTIVITY_NOT_ALLOWED',
|
|
1998
|
+
message: 'Guests cannot use activity tools.',
|
|
1999
|
+
},
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
1822
2002
|
if (!project) {
|
|
1823
2003
|
return {
|
|
1824
2004
|
success: false,
|
|
@@ -1961,11 +2141,15 @@ let McpService = class McpService {
|
|
|
1961
2141
|
matchingSessions = activeSessions.filter((s) => s.id.startsWith(sessionId));
|
|
1962
2142
|
}
|
|
1963
2143
|
if (matchingSessions.length === 0) {
|
|
2144
|
+
const guestContext = await this.tryResolveGuestContext(sessionId);
|
|
2145
|
+
if (guestContext) {
|
|
2146
|
+
return { success: true, data: guestContext };
|
|
2147
|
+
}
|
|
1964
2148
|
return {
|
|
1965
2149
|
success: false,
|
|
1966
2150
|
error: {
|
|
1967
2151
|
code: 'SESSION_NOT_FOUND',
|
|
1968
|
-
message: `No active session found matching '${sessionId}'`,
|
|
2152
|
+
message: `No active session or guest found matching '${sessionId}'`,
|
|
1969
2153
|
},
|
|
1970
2154
|
};
|
|
1971
2155
|
}
|
|
@@ -2013,6 +2197,7 @@ let McpService = class McpService {
|
|
|
2013
2197
|
}
|
|
2014
2198
|
}
|
|
2015
2199
|
const context = {
|
|
2200
|
+
type: 'agent',
|
|
2016
2201
|
session: {
|
|
2017
2202
|
id: session.id,
|
|
2018
2203
|
agentId: session.agentId,
|
|
@@ -2035,6 +2220,103 @@ let McpService = class McpService {
|
|
|
2035
2220
|
};
|
|
2036
2221
|
}
|
|
2037
2222
|
}
|
|
2223
|
+
async tryResolveGuestContext(guestId) {
|
|
2224
|
+
if (!this.guestsService || !this.tmuxService) {
|
|
2225
|
+
return null;
|
|
2226
|
+
}
|
|
2227
|
+
try {
|
|
2228
|
+
let guest;
|
|
2229
|
+
if (guestId.length === 36) {
|
|
2230
|
+
guest = await this.storage.getGuest(guestId);
|
|
2231
|
+
}
|
|
2232
|
+
else {
|
|
2233
|
+
const matches = await this.storage.getGuestsByIdPrefix(guestId);
|
|
2234
|
+
if (matches.length === 1) {
|
|
2235
|
+
guest = matches[0];
|
|
2236
|
+
}
|
|
2237
|
+
else {
|
|
2238
|
+
return null;
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
const sessionAlive = await this.tmuxService.hasSession(guest.tmuxSessionId);
|
|
2242
|
+
if (!sessionAlive) {
|
|
2243
|
+
logger.warn({ guestId: guest.id, tmuxSessionId: guest.tmuxSessionId }, 'Guest tmux session no longer exists');
|
|
2244
|
+
return null;
|
|
2245
|
+
}
|
|
2246
|
+
const project = await this.storage.getProject(guest.projectId);
|
|
2247
|
+
const context = {
|
|
2248
|
+
type: 'guest',
|
|
2249
|
+
guest: {
|
|
2250
|
+
id: guest.id,
|
|
2251
|
+
name: guest.name,
|
|
2252
|
+
projectId: guest.projectId,
|
|
2253
|
+
tmuxSessionId: guest.tmuxSessionId,
|
|
2254
|
+
},
|
|
2255
|
+
project: {
|
|
2256
|
+
id: project.id,
|
|
2257
|
+
name: project.name,
|
|
2258
|
+
rootPath: project.rootPath,
|
|
2259
|
+
},
|
|
2260
|
+
};
|
|
2261
|
+
return context;
|
|
2262
|
+
}
|
|
2263
|
+
catch (error) {
|
|
2264
|
+
logger.debug({ guestId: redactSessionId(guestId), error: String(error) }, 'Failed to resolve guest context');
|
|
2265
|
+
return null;
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
async registerGuest(params) {
|
|
2269
|
+
if (!this.guestsService) {
|
|
2270
|
+
return {
|
|
2271
|
+
success: false,
|
|
2272
|
+
error: {
|
|
2273
|
+
code: 'SERVICE_UNAVAILABLE',
|
|
2274
|
+
message: 'Guest registration requires full app context',
|
|
2275
|
+
},
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
const validated = mcp_dto_1.RegisterGuestParamsSchema.parse(params);
|
|
2279
|
+
try {
|
|
2280
|
+
const result = await this.guestsService.register({
|
|
2281
|
+
name: validated.name,
|
|
2282
|
+
tmuxSessionId: validated.tmuxSessionId,
|
|
2283
|
+
description: validated.description,
|
|
2284
|
+
});
|
|
2285
|
+
const response = {
|
|
2286
|
+
guestId: result.guestId,
|
|
2287
|
+
name: validated.name,
|
|
2288
|
+
projectId: result.projectId,
|
|
2289
|
+
projectName: result.projectName,
|
|
2290
|
+
isSandbox: result.isSandbox,
|
|
2291
|
+
registeredAt: new Date().toISOString(),
|
|
2292
|
+
};
|
|
2293
|
+
logger.info({ guestId: result.guestId, projectId: result.projectId, isSandbox: result.isSandbox }, 'Guest registered successfully');
|
|
2294
|
+
return { success: true, data: response };
|
|
2295
|
+
}
|
|
2296
|
+
catch (error) {
|
|
2297
|
+
if (error instanceof error_types_1.ValidationError) {
|
|
2298
|
+
return {
|
|
2299
|
+
success: false,
|
|
2300
|
+
error: {
|
|
2301
|
+
code: 'VALIDATION_ERROR',
|
|
2302
|
+
message: error.message,
|
|
2303
|
+
data: error.details,
|
|
2304
|
+
},
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
if (error instanceof Error && error.name === 'ConflictError') {
|
|
2308
|
+
return {
|
|
2309
|
+
success: false,
|
|
2310
|
+
error: {
|
|
2311
|
+
code: 'CONFLICT',
|
|
2312
|
+
message: error.message,
|
|
2313
|
+
data: error.data,
|
|
2314
|
+
},
|
|
2315
|
+
};
|
|
2316
|
+
}
|
|
2317
|
+
throw error;
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2038
2320
|
mapStatusSummary(status) {
|
|
2039
2321
|
return {
|
|
2040
2322
|
id: status.id,
|
|
@@ -2364,13 +2646,17 @@ exports.McpService = McpService = __decorate([
|
|
|
2364
2646
|
__param(2, (0, common_1.Inject)((0, common_1.forwardRef)(() => sessions_service_1.SessionsService))),
|
|
2365
2647
|
__param(3, (0, common_1.Inject)((0, common_1.forwardRef)(() => sessions_message_pool_service_1.SessionsMessagePoolService))),
|
|
2366
2648
|
__param(4, (0, common_1.Inject)((0, common_1.forwardRef)(() => terminal_gateway_1.TerminalGateway))),
|
|
2367
|
-
__param(5, (0, common_1.Inject)((0, common_1.forwardRef)(() =>
|
|
2368
|
-
__param(6, (0, common_1.Inject)((0, common_1.forwardRef)(() =>
|
|
2649
|
+
__param(5, (0, common_1.Inject)((0, common_1.forwardRef)(() => tmux_service_1.TmuxService))),
|
|
2650
|
+
__param(6, (0, common_1.Inject)((0, common_1.forwardRef)(() => epics_service_1.EpicsService))),
|
|
2651
|
+
__param(7, (0, common_1.Inject)((0, common_1.forwardRef)(() => settings_service_1.SettingsService))),
|
|
2652
|
+
__param(8, (0, common_1.Inject)((0, common_1.forwardRef)(() => guests_service_1.GuestsService))),
|
|
2369
2653
|
__metadata("design:paramtypes", [Object, chat_service_1.ChatService,
|
|
2370
2654
|
sessions_service_1.SessionsService,
|
|
2371
2655
|
sessions_message_pool_service_1.SessionsMessagePoolService,
|
|
2372
2656
|
terminal_gateway_1.TerminalGateway,
|
|
2657
|
+
tmux_service_1.TmuxService,
|
|
2373
2658
|
epics_service_1.EpicsService,
|
|
2374
|
-
settings_service_1.SettingsService
|
|
2659
|
+
settings_service_1.SettingsService,
|
|
2660
|
+
guests_service_1.GuestsService])
|
|
2375
2661
|
], McpService);
|
|
2376
2662
|
//# sourceMappingURL=mcp.service.js.map
|