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.
Files changed (152) hide show
  1. package/README.md +2 -0
  2. package/dist/cli.js +199 -42
  3. package/dist/drizzle/0018_whole_zodiak.sql +43 -0
  4. package/dist/drizzle/0019_flat_avengers.sql +3 -0
  5. package/dist/drizzle/0020_statuses_mcp_hidden.sql +1 -0
  6. package/dist/drizzle/meta/0018_snapshot.json +2920 -0
  7. package/dist/drizzle/meta/0019_snapshot.json +2943 -0
  8. package/dist/drizzle/meta/0020_snapshot.json +2951 -0
  9. package/dist/drizzle/meta/_journal.json +21 -0
  10. package/dist/server/app.module.js +11 -1
  11. package/dist/server/app.module.js.map +1 -1
  12. package/dist/server/common/config/env.config.d.ts +1 -0
  13. package/dist/server/common/config/env.config.js +4 -0
  14. package/dist/server/common/config/env.config.js.map +1 -1
  15. package/dist/server/common/filters/http-exception.filter.js +24 -1
  16. package/dist/server/common/filters/http-exception.filter.js.map +1 -1
  17. package/dist/server/common/logging/logger.js +4 -3
  18. package/dist/server/common/logging/logger.js.map +1 -1
  19. package/dist/server/main.js +0 -3
  20. package/dist/server/main.js.map +1 -1
  21. package/dist/server/modules/agents/agents.module.js +2 -1
  22. package/dist/server/modules/agents/agents.module.js.map +1 -1
  23. package/dist/server/modules/agents/controllers/agents.controller.d.ts +17 -2
  24. package/dist/server/modules/agents/controllers/agents.controller.js +84 -3
  25. package/dist/server/modules/agents/controllers/agents.controller.js.map +1 -1
  26. package/dist/server/modules/chat/dtos/chat.dto.d.ts +18 -18
  27. package/dist/server/modules/chat/services/invite-template.util.js +1 -1
  28. package/dist/server/modules/chat/services/invite-template.util.js.map +1 -1
  29. package/dist/server/modules/core/controllers/health.controller.d.ts +1 -0
  30. package/dist/server/modules/core/controllers/health.controller.js +23 -0
  31. package/dist/server/modules/core/controllers/health.controller.js.map +1 -1
  32. package/dist/server/modules/events/catalog/index.d.ts +40 -0
  33. package/dist/server/modules/events/catalog/index.js +2 -0
  34. package/dist/server/modules/events/catalog/index.js.map +1 -1
  35. package/dist/server/modules/events/catalog/terminal.watcher.triggered.d.ts +45 -0
  36. package/dist/server/modules/events/catalog/terminal.watcher.triggered.js +22 -0
  37. package/dist/server/modules/events/catalog/terminal.watcher.triggered.js.map +1 -0
  38. package/dist/server/modules/events/subscribers/chat-message-delivery.subscriber.d.ts +1 -0
  39. package/dist/server/modules/events/subscribers/chat-message-delivery.subscriber.js +30 -12
  40. package/dist/server/modules/events/subscribers/chat-message-delivery.subscriber.js.map +1 -1
  41. package/dist/server/modules/mcp/constants.js +5 -1
  42. package/dist/server/modules/mcp/constants.js.map +1 -1
  43. package/dist/server/modules/mcp/controllers/mcp-http.controller.js +78 -77
  44. package/dist/server/modules/mcp/controllers/mcp-http.controller.js.map +1 -1
  45. package/dist/server/modules/mcp/controllers/mcp-sdk.controller.js +78 -77
  46. package/dist/server/modules/mcp/controllers/mcp-sdk.controller.js.map +1 -1
  47. package/dist/server/modules/mcp/dtos/mcp.dto.d.ts +125 -97
  48. package/dist/server/modules/mcp/dtos/mcp.dto.js +23 -26
  49. package/dist/server/modules/mcp/dtos/mcp.dto.js.map +1 -1
  50. package/dist/server/modules/mcp/services/instructions-resolver.d.ts +3 -0
  51. package/dist/server/modules/mcp/services/instructions-resolver.js +83 -2
  52. package/dist/server/modules/mcp/services/instructions-resolver.js.map +1 -1
  53. package/dist/server/modules/mcp/services/mcp.service.d.ts +3 -2
  54. package/dist/server/modules/mcp/services/mcp.service.js +549 -263
  55. package/dist/server/modules/mcp/services/mcp.service.js.map +1 -1
  56. package/dist/server/modules/projects/controllers/projects.controller.d.ts +42 -0
  57. package/dist/server/modules/projects/controllers/projects.controller.js +11 -0
  58. package/dist/server/modules/projects/controllers/projects.controller.js.map +1 -1
  59. package/dist/server/modules/projects/projects.module.js +2 -1
  60. package/dist/server/modules/projects/projects.module.js.map +1 -1
  61. package/dist/server/modules/projects/services/projects.service.d.ts +47 -1
  62. package/dist/server/modules/projects/services/projects.service.js +278 -22
  63. package/dist/server/modules/projects/services/projects.service.js.map +1 -1
  64. package/dist/server/modules/prompts/controllers/prompts.controller.d.ts +1 -1
  65. package/dist/server/modules/prompts/controllers/prompts.controller.js +26 -4
  66. package/dist/server/modules/prompts/controllers/prompts.controller.js.map +1 -1
  67. package/dist/server/modules/sessions/utils/template-renderer.js +1 -0
  68. package/dist/server/modules/sessions/utils/template-renderer.js.map +1 -1
  69. package/dist/server/modules/statuses/controllers/statuses.controller.js +2 -0
  70. package/dist/server/modules/statuses/controllers/statuses.controller.js.map +1 -1
  71. package/dist/server/modules/storage/db/schema.d.ts +613 -0
  72. package/dist/server/modules/storage/db/schema.js +50 -1
  73. package/dist/server/modules/storage/db/schema.js.map +1 -1
  74. package/dist/server/modules/storage/interfaces/storage.interface.d.ts +40 -2
  75. package/dist/server/modules/storage/interfaces/storage.interface.js.map +1 -1
  76. package/dist/server/modules/storage/local/local-storage.service.d.ts +18 -3
  77. package/dist/server/modules/storage/local/local-storage.service.js +407 -11
  78. package/dist/server/modules/storage/local/local-storage.service.js.map +1 -1
  79. package/dist/server/modules/storage/models/domain.models.d.ts +59 -1
  80. package/dist/server/modules/subscribers/actions/action.interface.d.ts +67 -0
  81. package/dist/server/modules/subscribers/actions/action.interface.js +3 -0
  82. package/dist/server/modules/subscribers/actions/action.interface.js.map +1 -0
  83. package/dist/server/modules/subscribers/actions/actions.registry.d.ts +7 -0
  84. package/dist/server/modules/subscribers/actions/actions.registry.js +37 -0
  85. package/dist/server/modules/subscribers/actions/actions.registry.js.map +1 -0
  86. package/dist/server/modules/subscribers/actions/restart-agent.action.d.ts +8 -0
  87. package/dist/server/modules/subscribers/actions/restart-agent.action.js +119 -0
  88. package/dist/server/modules/subscribers/actions/restart-agent.action.js.map +1 -0
  89. package/dist/server/modules/subscribers/actions/send-message.action.d.ts +2 -0
  90. package/dist/server/modules/subscribers/actions/send-message.action.js +83 -0
  91. package/dist/server/modules/subscribers/actions/send-message.action.js.map +1 -0
  92. package/dist/server/modules/subscribers/controllers/actions.controller.d.ts +6 -0
  93. package/dist/server/modules/subscribers/controllers/actions.controller.js +51 -0
  94. package/dist/server/modules/subscribers/controllers/actions.controller.js.map +1 -0
  95. package/dist/server/modules/subscribers/controllers/subscribers.controller.d.ts +17 -0
  96. package/dist/server/modules/subscribers/controllers/subscribers.controller.js +178 -0
  97. package/dist/server/modules/subscribers/controllers/subscribers.controller.js.map +1 -0
  98. package/dist/server/modules/subscribers/dtos/subscriber.dto.d.ts +251 -0
  99. package/dist/server/modules/subscribers/dtos/subscriber.dto.js +68 -0
  100. package/dist/server/modules/subscribers/dtos/subscriber.dto.js.map +1 -0
  101. package/dist/server/modules/subscribers/events/event-fields-catalog.d.ts +19 -0
  102. package/dist/server/modules/subscribers/events/event-fields-catalog.js +98 -0
  103. package/dist/server/modules/subscribers/events/event-fields-catalog.js.map +1 -0
  104. package/dist/server/modules/subscribers/services/automation-scheduler.service.d.ts +49 -0
  105. package/dist/server/modules/subscribers/services/automation-scheduler.service.js +300 -0
  106. package/dist/server/modules/subscribers/services/automation-scheduler.service.js.map +1 -0
  107. package/dist/server/modules/subscribers/services/subscriber-executor.service.d.ts +77 -0
  108. package/dist/server/modules/subscribers/services/subscriber-executor.service.js +576 -0
  109. package/dist/server/modules/subscribers/services/subscriber-executor.service.js.map +1 -0
  110. package/dist/server/modules/subscribers/services/subscribers.service.d.ts +14 -0
  111. package/dist/server/modules/subscribers/services/subscribers.service.js +70 -0
  112. package/dist/server/modules/subscribers/services/subscribers.service.js.map +1 -0
  113. package/dist/server/modules/subscribers/subscribers.module.d.ts +2 -0
  114. package/dist/server/modules/subscribers/subscribers.module.js +36 -0
  115. package/dist/server/modules/subscribers/subscribers.module.js.map +1 -0
  116. package/dist/server/modules/terminal/services/tmux.service.js +9 -6
  117. package/dist/server/modules/terminal/services/tmux.service.js.map +1 -1
  118. package/dist/server/modules/watchers/controllers/watchers.controller.d.ts +16 -0
  119. package/dist/server/modules/watchers/controllers/watchers.controller.js +180 -0
  120. package/dist/server/modules/watchers/controllers/watchers.controller.js.map +1 -0
  121. package/dist/server/modules/watchers/dtos/watcher.dto.d.ts +206 -0
  122. package/dist/server/modules/watchers/dtos/watcher.dto.js +54 -0
  123. package/dist/server/modules/watchers/dtos/watcher.dto.js.map +1 -0
  124. package/dist/server/modules/watchers/services/watcher-runner.service.d.ts +68 -0
  125. package/dist/server/modules/watchers/services/watcher-runner.service.js +477 -0
  126. package/dist/server/modules/watchers/services/watcher-runner.service.js.map +1 -0
  127. package/dist/server/modules/watchers/services/watchers.service.d.ts +29 -0
  128. package/dist/server/modules/watchers/services/watchers.service.js +98 -0
  129. package/dist/server/modules/watchers/services/watchers.service.js.map +1 -0
  130. package/dist/server/modules/watchers/watchers.module.d.ts +2 -0
  131. package/dist/server/modules/watchers/watchers.module.js +34 -0
  132. package/dist/server/modules/watchers/watchers.module.js.map +1 -0
  133. package/dist/server/templates/claude-codex-advanced.json +377 -0
  134. package/dist/server/templates/claude-opus.json +29 -25
  135. package/dist/server/templates/simple-codex.json +29 -25
  136. package/dist/server/test-setup-node.d.ts +1 -0
  137. package/dist/server/test-setup-node.js +8 -0
  138. package/dist/server/test-setup-node.js.map +1 -0
  139. package/dist/server/test-setup.js +2 -0
  140. package/dist/server/test-setup.js.map +1 -1
  141. package/dist/server/tsconfig.tsbuildinfo +1 -1
  142. package/dist/server/ui/assets/index-C9GXCjnF.js +700 -0
  143. package/dist/server/ui/assets/index-o0FbZg-1.css +32 -0
  144. package/dist/server/ui/index.html +2 -2
  145. package/dist/templates/claude-codex-advanced.json +377 -0
  146. package/dist/templates/claude-opus.json +29 -25
  147. package/dist/templates/simple-codex.json +29 -25
  148. package/package.json +58 -27
  149. package/dist/server/templates/codex-claude.json +0 -178
  150. package/dist/server/ui/assets/index-5Xb7jFMJ.js +0 -641
  151. package/dist/server/ui/assets/index-CbYIbCQV.css +0 -32
  152. 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 pathValidation = this.validateAbsolutePath(validated.path);
340
- if (pathValidation) {
341
- return pathValidation;
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: true,
348
- data: {
349
- documents: [],
350
- total: 0,
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({ path: validated.path, projectId: project.id }, 'Resolved path to project');
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 pathValidation = this.validateAbsolutePath(validated.path);
411
- if (pathValidation) {
412
- return pathValidation;
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: `No project found for path: ${validated.path}`,
388
+ message: 'No project associated with this session',
421
389
  },
422
390
  };
423
391
  }
424
- logger.debug({ path: validated.path, projectId: project.id }, 'Resolved path to project for document creation');
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
- let projectId;
454
- if (validated.path) {
455
- const pathCheck = this.validateAbsolutePath(validated.path);
456
- if (pathCheck) {
457
- return pathCheck;
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: { code: 'INVALID_PARAMS', message: 'path is required for devchain_list_prompts' },
428
+ error: {
429
+ code: 'PROJECT_NOT_FOUND',
430
+ message: 'No project associated with this session',
431
+ },
474
432
  };
475
433
  }
476
- const result = await this.storage.listPrompts(projectId ?? null, undefined);
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
- let projectId;
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: 'path is required when querying prompt by name',
461
+ message: 'sessionId is required when querying prompt by name',
523
462
  },
524
463
  };
525
464
  }
526
- const list = await this.storage.listPrompts(projectId ?? null, undefined);
527
- prompt = list.items.find((item) => {
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 (prompt) {
537
- prompt = await this.storage.getPrompt(prompt.id);
486
+ if (found) {
487
+ prompt = await this.storage.getPrompt(found.id);
538
488
  }
539
489
  }
540
490
  if (!prompt) {
541
- throw new Error('Prompt not found');
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.mapPromptSummary(prompt),
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 pathValidation = this.validateAbsolutePath(validated.path);
551
- if (pathValidation) {
552
- return pathValidation;
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: true,
559
- data: {
560
- agents: [],
561
- total: 0,
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 pathValidation = this.validateAbsolutePath(validated.path);
593
- if (pathValidation) {
594
- return pathValidation;
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: `No project found for path: ${validated.path}`,
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 resolution = await this.resolveProject(validated.path);
670
- if (!resolution.project) {
671
- return resolution.error;
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(resolution.project.id, {
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 resolution = await this.resolveProject(validated.path);
685
- if (!resolution.project) {
686
- return resolution.error;
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 resolution = await this.resolveProject(validated.path);
753
- if (!resolution.project) {
754
- return resolution.error;
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 resolution = await this.resolveProject(validated.path);
838
- if (!resolution.project) {
839
- return resolution.error;
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 resolution = await this.resolveProject(validated.path);
914
- if (!resolution.project) {
915
- return resolution.error;
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 resolution = await this.resolveProject(validated.path);
1017
- if (!resolution.project) {
1018
- return resolution.error;
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: validated.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 resolution = await this.resolveProject(validated.path);
1079
- if (!resolution.project) {
1080
- return resolution.error;
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.chatService || !this.sessionsService) {
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 resolution = await this.resolveProject(validated.path);
1275
- if (!resolution.project) {
1276
- return resolution.error;
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
- let senderAgentId = null;
1281
- const validatedWithRecipient = validated;
1282
- const recipientType = validatedWithRecipient.recipient ?? 'agents';
1283
- const senderNameInput = validated.senderName ?? validatedWithRecipient.agentName;
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: 'threadId is required when senderName is not provided',
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: senderAgentId ? 'agent' : 'user',
1340
- authorAgentId: senderAgentId ?? undefined,
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
- const session = activeSessions.find((s) => s.agentId === agentId);
1355
- if (session) {
1356
- const senderName = senderAgentId
1357
- ? (await this.storage.getAgent(senderAgentId)).name
1358
- : 'User';
1359
- const injectionText = `\n[CHAT] From: ${senderName} • Thread: ${threadId}\n${validated.message}\n[ACK] tools/call { name: "devchain_chat_ack", arguments: { thread_id: "${threadId}", message_id: "${message.id}", agent_name: "${agent.name}" } }\n`;
1360
- await this.sessionsService.injectTextIntoSession(session.id, injectionText);
1361
- delivered.push({
1362
- agentId,
1363
- agentName: agent.name,
1364
- sessionId: session.id,
1365
- status: 'delivered',
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
- else {
1369
- delivered.push({
1370
- agentId,
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
- let agentId = null;
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
- let resolved = null;
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 ${validated.agent_name} is not a member of thread ${threadId}`,
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: 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: 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 resolution = await this.resolveProject(validated.path);
1647
- if (!resolution.project)
1648
- return resolution.error;
1649
- const project = resolution.project;
1650
- const agentRes = await this.resolveAgentByNameUnique(project.id, validated.agentName);
1651
- if (!('id' in agentRes))
1652
- return agentRes;
1653
- const agentId = agentRes.id;
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 ${validated.agentName} is not a member of thread ${threadId}`,
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 resolution = await this.resolveProject(validated.path);
1693
- if (!resolution.project)
1694
- return resolution.error;
1695
- const project = resolution.project;
1696
- const agentRes = await this.resolveAgentByNameUnique(project.id, validated.agentName);
1697
- if (!('id' in agentRes))
1698
- return agentRes;
1699
- const agentId = agentRes.id;
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 ${validated.agentName} is not a member of thread ${threadId}`,
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, undefined);
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.mapPromptSummary(prompt),
2312
+ prompt: this.mapPromptDetail(prompt),
2027
2313
  },
2028
2314
  };
2029
2315
  }