devchain-cli 0.10.4 → 0.11.0

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 (363) hide show
  1. package/README.md +13 -7
  2. package/dist/cli.js +55 -63
  3. package/dist/drizzle/0048_sessions_transcript_fields.sql +6 -0
  4. package/dist/drizzle/0049_provider_models_and_agent_model_override.sql +24 -0
  5. package/dist/drizzle/meta/_journal.json +14 -0
  6. package/dist/node_modules/@devchain/shared/schemas/export-schema.d.ts +25 -0
  7. package/dist/node_modules/@devchain/shared/schemas/export-schema.d.ts.map +1 -1
  8. package/dist/node_modules/@devchain/shared/schemas/export-schema.js +9 -3
  9. package/dist/node_modules/@devchain/shared/schemas/export-schema.js.map +1 -1
  10. package/dist/node_modules/@devchain/shared/tsconfig.tsbuildinfo +1 -1
  11. package/dist/server/app.main.module.js +2 -0
  12. package/dist/server/app.main.module.js.map +1 -1
  13. package/dist/server/app.normal.module.js +2 -0
  14. package/dist/server/app.normal.module.js.map +1 -1
  15. package/dist/server/common/config/env.config.js +1 -10
  16. package/dist/server/common/config/env.config.js.map +1 -1
  17. package/dist/server/modules/agents/controllers/agents.controller.d.ts +1 -0
  18. package/dist/server/modules/agents/controllers/agents.controller.js +3 -0
  19. package/dist/server/modules/agents/controllers/agents.controller.js.map +1 -1
  20. package/dist/server/modules/chat/dtos/chat.dto.d.ts +4 -4
  21. package/dist/server/modules/core/services/preflight.service.js +7 -0
  22. package/dist/server/modules/core/services/preflight.service.js.map +1 -1
  23. package/dist/server/modules/core/services/provider-mcp-ensure.service.js +8 -0
  24. package/dist/server/modules/core/services/provider-mcp-ensure.service.js.map +1 -1
  25. package/dist/server/modules/documents/controllers/documents.controller.d.ts +2 -2
  26. package/dist/server/modules/documents/controllers/documents.controller.js.map +1 -1
  27. package/dist/server/modules/epics/controllers/epic-comments.controller.d.ts +2 -2
  28. package/dist/server/modules/epics/controllers/epic-comments.controller.js.map +1 -1
  29. package/dist/server/modules/events/catalog/claude.hooks.session.started.d.ts +4 -4
  30. package/dist/server/modules/events/catalog/index.d.ts +115 -4
  31. package/dist/server/modules/events/catalog/index.js +6 -0
  32. package/dist/server/modules/events/catalog/index.js.map +1 -1
  33. package/dist/server/modules/events/catalog/session.transcript.discovered.d.ts +24 -0
  34. package/dist/server/modules/events/catalog/session.transcript.discovered.js +15 -0
  35. package/dist/server/modules/events/catalog/session.transcript.discovered.js.map +1 -0
  36. package/dist/server/modules/events/catalog/session.transcript.ended.d.ts +51 -0
  37. package/dist/server/modules/events/catalog/session.transcript.ended.js +20 -0
  38. package/dist/server/modules/events/catalog/session.transcript.ended.js.map +1 -0
  39. package/dist/server/modules/events/catalog/session.transcript.updated.d.ts +51 -0
  40. package/dist/server/modules/events/catalog/session.transcript.updated.js +20 -0
  41. package/dist/server/modules/events/catalog/session.transcript.updated.js.map +1 -0
  42. package/dist/server/modules/events/subscribers/chat-message-delivery.subscriber.d.ts +2 -2
  43. package/dist/server/modules/events/subscribers/chat-message-delivery.subscriber.js.map +1 -1
  44. package/dist/server/modules/events/subscribers/index.js +2 -0
  45. package/dist/server/modules/events/subscribers/index.js.map +1 -1
  46. package/dist/server/modules/events/subscribers/review-comment-notifier.subscriber.d.ts +2 -2
  47. package/dist/server/modules/events/subscribers/review-comment-notifier.subscriber.js.map +1 -1
  48. package/dist/server/modules/events/subscribers/transcript-broadcaster.subscriber.d.ts +12 -0
  49. package/dist/server/modules/events/subscribers/transcript-broadcaster.subscriber.js +88 -0
  50. package/dist/server/modules/events/subscribers/transcript-broadcaster.subscriber.js.map +1 -0
  51. package/dist/server/modules/guests/services/guest-health.service.d.ts +2 -2
  52. package/dist/server/modules/guests/services/guest-health.service.js.map +1 -1
  53. package/dist/server/modules/hooks/dtos/hook-event.dto.d.ts +4 -4
  54. package/dist/server/modules/hooks/services/hooks.service.d.ts +2 -2
  55. package/dist/server/modules/hooks/services/hooks.service.js.map +1 -1
  56. package/dist/server/modules/mcp/dtos/mcp.dto.js +8 -3
  57. package/dist/server/modules/mcp/dtos/mcp.dto.js.map +1 -1
  58. package/dist/server/modules/mcp/services/handlers/activity-tools.d.ts +4 -0
  59. package/dist/server/modules/mcp/services/handlers/activity-tools.js +193 -0
  60. package/dist/server/modules/mcp/services/handlers/activity-tools.js.map +1 -0
  61. package/dist/server/modules/mcp/services/handlers/chat-tools.d.ts +6 -0
  62. package/dist/server/modules/mcp/services/handlers/chat-tools.js +605 -0
  63. package/dist/server/modules/mcp/services/handlers/chat-tools.js.map +1 -0
  64. package/dist/server/modules/mcp/services/handlers/document-tools.d.ts +6 -0
  65. package/dist/server/modules/mcp/services/handlers/document-tools.js +141 -0
  66. package/dist/server/modules/mcp/services/handlers/document-tools.js.map +1 -0
  67. package/dist/server/modules/mcp/services/handlers/epic-tools.d.ts +11 -0
  68. package/dist/server/modules/mcp/services/handlers/epic-tools.js +913 -0
  69. package/dist/server/modules/mcp/services/handlers/epic-tools.js.map +1 -0
  70. package/dist/server/modules/mcp/services/handlers/prompt-tools.d.ts +4 -0
  71. package/dist/server/modules/mcp/services/handlers/prompt-tools.js +109 -0
  72. package/dist/server/modules/mcp/services/handlers/prompt-tools.js.map +1 -0
  73. package/dist/server/modules/mcp/services/handlers/record-tools.d.ts +8 -0
  74. package/dist/server/modules/mcp/services/handlers/record-tools.js +114 -0
  75. package/dist/server/modules/mcp/services/handlers/record-tools.js.map +1 -0
  76. package/dist/server/modules/mcp/services/handlers/review-tools.d.ts +8 -0
  77. package/dist/server/modules/mcp/services/handlers/review-tools.js +672 -0
  78. package/dist/server/modules/mcp/services/handlers/review-tools.js.map +1 -0
  79. package/dist/server/modules/mcp/services/handlers/session-tools.d.ts +4 -0
  80. package/dist/server/modules/mcp/services/handlers/session-tools.js +114 -0
  81. package/dist/server/modules/mcp/services/handlers/session-tools.js.map +1 -0
  82. package/dist/server/modules/mcp/services/handlers/skill-tools.d.ts +4 -0
  83. package/dist/server/modules/mcp/services/handlers/skill-tools.js +124 -0
  84. package/dist/server/modules/mcp/services/handlers/skill-tools.js.map +1 -0
  85. package/dist/server/modules/mcp/services/handlers/types.d.ts +33 -0
  86. package/dist/server/modules/mcp/services/handlers/types.js +3 -0
  87. package/dist/server/modules/mcp/services/handlers/types.js.map +1 -0
  88. package/dist/server/modules/mcp/services/mappers/dto-mappers.d.ts +14 -0
  89. package/dist/server/modules/mcp/services/mappers/dto-mappers.js +138 -0
  90. package/dist/server/modules/mcp/services/mappers/dto-mappers.js.map +1 -0
  91. package/dist/server/modules/mcp/services/mcp-provider-registration.service.d.ts +8 -0
  92. package/dist/server/modules/mcp/services/mcp-provider-registration.service.js +258 -51
  93. package/dist/server/modules/mcp/services/mcp-provider-registration.service.js.map +1 -1
  94. package/dist/server/modules/mcp/services/mcp.service.d.ts +6 -66
  95. package/dist/server/modules/mcp/services/mcp.service.js +97 -3329
  96. package/dist/server/modules/mcp/services/mcp.service.js.map +1 -1
  97. package/dist/server/modules/mcp/services/utils/document-link-resolver.d.ts +10 -0
  98. package/dist/server/modules/mcp/services/utils/document-link-resolver.js +124 -0
  99. package/dist/server/modules/mcp/services/utils/document-link-resolver.js.map +1 -0
  100. package/dist/server/modules/mcp/services/utils/resolve-epic-id.d.ts +3 -0
  101. package/dist/server/modules/mcp/services/utils/resolve-epic-id.js +40 -0
  102. package/dist/server/modules/mcp/services/utils/resolve-epic-id.js.map +1 -0
  103. package/dist/server/modules/mcp/services/utils/resource-resolver.d.ts +10 -0
  104. package/dist/server/modules/mcp/services/utils/resource-resolver.js +124 -0
  105. package/dist/server/modules/mcp/services/utils/resource-resolver.js.map +1 -0
  106. package/dist/server/modules/mcp/services/utils/session-context-resolver.d.ts +14 -0
  107. package/dist/server/modules/mcp/services/utils/session-context-resolver.js +169 -0
  108. package/dist/server/modules/mcp/services/utils/session-context-resolver.js.map +1 -0
  109. package/dist/server/modules/mcp/tool-definitions.js +12 -3
  110. package/dist/server/modules/mcp/tool-definitions.js.map +1 -1
  111. package/dist/server/modules/orchestrator/docker/services/docker.service.js +16 -2
  112. package/dist/server/modules/orchestrator/docker/services/docker.service.js.map +1 -1
  113. package/dist/server/modules/orchestrator/ui/app/lib/worktrees.js +9 -0
  114. package/dist/server/modules/orchestrator/ui/app/lib/worktrees.js.map +1 -1
  115. package/dist/server/modules/profiles/controllers/provider-configs.controller.d.ts +2 -2
  116. package/dist/server/modules/profiles/controllers/provider-configs.controller.js.map +1 -1
  117. package/dist/server/modules/profiles/dto.d.ts +2 -2
  118. package/dist/server/modules/projects/controllers/projects.controller.d.ts +38 -33
  119. package/dist/server/modules/projects/controllers/projects.controller.js +12 -7
  120. package/dist/server/modules/projects/controllers/projects.controller.js.map +1 -1
  121. package/dist/server/modules/projects/dtos/export.dto.d.ts +7 -0
  122. package/dist/server/modules/projects/dtos/export.dto.js +1 -0
  123. package/dist/server/modules/projects/dtos/export.dto.js.map +1 -1
  124. package/dist/server/modules/projects/helpers/profile-mapping.helpers.d.ts +102 -0
  125. package/dist/server/modules/projects/helpers/profile-mapping.helpers.js +369 -0
  126. package/dist/server/modules/projects/helpers/profile-mapping.helpers.js.map +1 -0
  127. package/dist/server/modules/projects/helpers/project-export.d.ts +141 -0
  128. package/dist/server/modules/projects/helpers/project-export.js +314 -0
  129. package/dist/server/modules/projects/helpers/project-export.js.map +1 -0
  130. package/dist/server/modules/projects/helpers/project-import.d.ts +132 -0
  131. package/dist/server/modules/projects/helpers/project-import.js +600 -0
  132. package/dist/server/modules/projects/helpers/project-import.js.map +1 -0
  133. package/dist/server/modules/projects/helpers/project-presets.helpers.d.ts +26 -0
  134. package/dist/server/modules/projects/helpers/project-presets.helpers.js +110 -0
  135. package/dist/server/modules/projects/helpers/project-presets.helpers.js.map +1 -0
  136. package/dist/server/modules/projects/helpers/project-runtime.helpers.d.ts +69 -0
  137. package/dist/server/modules/projects/helpers/project-runtime.helpers.js +201 -0
  138. package/dist/server/modules/projects/helpers/project-runtime.helpers.js.map +1 -0
  139. package/dist/server/modules/projects/helpers/project-template-manifest.helpers.d.ts +16 -0
  140. package/dist/server/modules/projects/helpers/project-template-manifest.helpers.js +107 -0
  141. package/dist/server/modules/projects/helpers/project-template-manifest.helpers.js.map +1 -0
  142. package/dist/server/modules/projects/helpers/template-file.helpers.d.ts +9 -0
  143. package/dist/server/modules/projects/helpers/template-file.helpers.js +92 -0
  144. package/dist/server/modules/projects/helpers/template-file.helpers.js.map +1 -0
  145. package/dist/server/modules/projects/helpers/template-loader.d.ts +85 -0
  146. package/dist/server/modules/projects/helpers/template-loader.js +406 -0
  147. package/dist/server/modules/projects/helpers/template-loader.js.map +1 -0
  148. package/dist/server/modules/projects/services/main-project-bootstrap.service.js +4 -0
  149. package/dist/server/modules/projects/services/main-project-bootstrap.service.js.map +1 -1
  150. package/dist/server/modules/projects/services/projects.service.d.ts +29 -54
  151. package/dist/server/modules/projects/services/projects.service.js +46 -1704
  152. package/dist/server/modules/projects/services/projects.service.js.map +1 -1
  153. package/dist/server/modules/prompts/controllers/prompts.controller.d.ts +2 -2
  154. package/dist/server/modules/prompts/controllers/prompts.controller.js.map +1 -1
  155. package/dist/server/modules/providers/adapters/index.d.ts +1 -0
  156. package/dist/server/modules/providers/adapters/index.js +1 -0
  157. package/dist/server/modules/providers/adapters/index.js.map +1 -1
  158. package/dist/server/modules/providers/adapters/opencode.adapter.d.ts +16 -0
  159. package/dist/server/modules/providers/adapters/opencode.adapter.js +64 -0
  160. package/dist/server/modules/providers/adapters/opencode.adapter.js.map +1 -0
  161. package/dist/server/modules/providers/adapters/provider-adapter.factory.d.ts +2 -1
  162. package/dist/server/modules/providers/adapters/provider-adapter.factory.js +5 -2
  163. package/dist/server/modules/providers/adapters/provider-adapter.factory.js.map +1 -1
  164. package/dist/server/modules/providers/adapters/provider-adapter.interface.d.ts +1 -0
  165. package/dist/server/modules/providers/adapters/provider-adapters.module.js +2 -1
  166. package/dist/server/modules/providers/adapters/provider-adapters.module.js.map +1 -1
  167. package/dist/server/modules/providers/controllers/provider-models.controller.d.ts +22 -0
  168. package/dist/server/modules/providers/controllers/provider-models.controller.js +173 -0
  169. package/dist/server/modules/providers/controllers/provider-models.controller.js.map +1 -0
  170. package/dist/server/modules/providers/providers.module.js +2 -1
  171. package/dist/server/modules/providers/providers.module.js.map +1 -1
  172. package/dist/server/modules/records/controllers/records.controller.d.ts +2 -2
  173. package/dist/server/modules/records/controllers/records.controller.js.map +1 -1
  174. package/dist/server/modules/registry/services/template-upgrade.service.js +1 -9
  175. package/dist/server/modules/registry/services/template-upgrade.service.js.map +1 -1
  176. package/dist/server/modules/session-reader/adapters/claude-session-reader.adapter.d.ts +21 -0
  177. package/dist/server/modules/session-reader/adapters/claude-session-reader.adapter.js +207 -0
  178. package/dist/server/modules/session-reader/adapters/claude-session-reader.adapter.js.map +1 -0
  179. package/dist/server/modules/session-reader/adapters/codex-session-reader.adapter.d.ts +22 -0
  180. package/dist/server/modules/session-reader/adapters/codex-session-reader.adapter.js +214 -0
  181. package/dist/server/modules/session-reader/adapters/codex-session-reader.adapter.js.map +1 -0
  182. package/dist/server/modules/session-reader/adapters/gemini-session-reader.adapter.d.ts +23 -0
  183. package/dist/server/modules/session-reader/adapters/gemini-session-reader.adapter.js +224 -0
  184. package/dist/server/modules/session-reader/adapters/gemini-session-reader.adapter.js.map +1 -0
  185. package/dist/server/modules/session-reader/adapters/session-reader-adapter.factory.d.ts +9 -0
  186. package/dist/server/modules/session-reader/adapters/session-reader-adapter.factory.js +40 -0
  187. package/dist/server/modules/session-reader/adapters/session-reader-adapter.factory.js.map +1 -0
  188. package/dist/server/modules/session-reader/adapters/session-reader-adapter.interface.d.ts +37 -0
  189. package/dist/server/modules/session-reader/adapters/session-reader-adapter.interface.js +3 -0
  190. package/dist/server/modules/session-reader/adapters/session-reader-adapter.interface.js.map +1 -0
  191. package/dist/server/modules/session-reader/adapters/utils/estimate-content-tokens.d.ts +11 -0
  192. package/dist/server/modules/session-reader/adapters/utils/estimate-content-tokens.js +77 -0
  193. package/dist/server/modules/session-reader/adapters/utils/estimate-content-tokens.js.map +1 -0
  194. package/dist/server/modules/session-reader/adapters/utils/file-search.util.d.ts +1 -0
  195. package/dist/server/modules/session-reader/adapters/utils/file-search.util.js +22 -0
  196. package/dist/server/modules/session-reader/adapters/utils/file-search.util.js.map +1 -0
  197. package/dist/server/modules/session-reader/builders/chunk-builder.d.ts +5 -0
  198. package/dist/server/modules/session-reader/builders/chunk-builder.js +131 -0
  199. package/dist/server/modules/session-reader/builders/chunk-builder.js.map +1 -0
  200. package/dist/server/modules/session-reader/builders/semantic-step-extractor.d.ts +3 -0
  201. package/dist/server/modules/session-reader/builders/semantic-step-extractor.js +93 -0
  202. package/dist/server/modules/session-reader/builders/semantic-step-extractor.js.map +1 -0
  203. package/dist/server/modules/session-reader/builders/turn-builder.d.ts +3 -0
  204. package/dist/server/modules/session-reader/builders/turn-builder.js +102 -0
  205. package/dist/server/modules/session-reader/builders/turn-builder.js.map +1 -0
  206. package/dist/server/modules/session-reader/controllers/session-reader.controller.d.ts +430 -0
  207. package/dist/server/modules/session-reader/controllers/session-reader.controller.js +252 -0
  208. package/dist/server/modules/session-reader/controllers/session-reader.controller.js.map +1 -0
  209. package/dist/server/modules/session-reader/data/pricing.json +5001 -0
  210. package/dist/server/modules/session-reader/dtos/index.d.ts +2 -0
  211. package/dist/server/modules/session-reader/dtos/index.js +10 -0
  212. package/dist/server/modules/session-reader/dtos/index.js.map +1 -0
  213. package/dist/server/modules/session-reader/dtos/unified-chunk.types.d.ts +92 -0
  214. package/dist/server/modules/session-reader/dtos/unified-chunk.types.js +19 -0
  215. package/dist/server/modules/session-reader/dtos/unified-chunk.types.js.map +1 -0
  216. package/dist/server/modules/session-reader/dtos/unified-session.types.d.ts +118 -0
  217. package/dist/server/modules/session-reader/dtos/unified-session.types.js +23 -0
  218. package/dist/server/modules/session-reader/dtos/unified-session.types.js.map +1 -0
  219. package/dist/server/modules/session-reader/parsers/claude-jsonl.parser.d.ts +15 -0
  220. package/dist/server/modules/session-reader/parsers/claude-jsonl.parser.js +363 -0
  221. package/dist/server/modules/session-reader/parsers/claude-jsonl.parser.js.map +1 -0
  222. package/dist/server/modules/session-reader/parsers/codex-jsonl.parser.d.ts +16 -0
  223. package/dist/server/modules/session-reader/parsers/codex-jsonl.parser.js +622 -0
  224. package/dist/server/modules/session-reader/parsers/codex-jsonl.parser.js.map +1 -0
  225. package/dist/server/modules/session-reader/parsers/gemini-json.parser.d.ts +15 -0
  226. package/dist/server/modules/session-reader/parsers/gemini-json.parser.js +380 -0
  227. package/dist/server/modules/session-reader/parsers/gemini-json.parser.js.map +1 -0
  228. package/dist/server/modules/session-reader/services/pricing.interface.d.ts +9 -0
  229. package/dist/server/modules/session-reader/services/pricing.interface.js +24 -0
  230. package/dist/server/modules/session-reader/services/pricing.interface.js.map +1 -0
  231. package/dist/server/modules/session-reader/services/pricing.service.d.ts +21 -0
  232. package/dist/server/modules/session-reader/services/pricing.service.js +106 -0
  233. package/dist/server/modules/session-reader/services/pricing.service.js.map +1 -0
  234. package/dist/server/modules/session-reader/services/session-cache.service.d.ts +24 -0
  235. package/dist/server/modules/session-reader/services/session-cache.service.js +203 -0
  236. package/dist/server/modules/session-reader/services/session-cache.service.js.map +1 -0
  237. package/dist/server/modules/session-reader/services/session-reader.service.d.ts +56 -0
  238. package/dist/server/modules/session-reader/services/session-reader.service.js +305 -0
  239. package/dist/server/modules/session-reader/services/session-reader.service.js.map +1 -0
  240. package/dist/server/modules/session-reader/services/subagent-locator.service.d.ts +10 -0
  241. package/dist/server/modules/session-reader/services/subagent-locator.service.js +102 -0
  242. package/dist/server/modules/session-reader/services/subagent-locator.service.js.map +1 -0
  243. package/dist/server/modules/session-reader/services/subagent-resolver.service.d.ts +22 -0
  244. package/dist/server/modules/session-reader/services/subagent-resolver.service.js +211 -0
  245. package/dist/server/modules/session-reader/services/subagent-resolver.service.js.map +1 -0
  246. package/dist/server/modules/session-reader/services/transcript-path-validator.service.d.ts +7 -0
  247. package/dist/server/modules/session-reader/services/transcript-path-validator.service.js +150 -0
  248. package/dist/server/modules/session-reader/services/transcript-path-validator.service.js.map +1 -0
  249. package/dist/server/modules/session-reader/services/transcript-persistence.listener.d.ts +30 -0
  250. package/dist/server/modules/session-reader/services/transcript-persistence.listener.js +336 -0
  251. package/dist/server/modules/session-reader/services/transcript-persistence.listener.js.map +1 -0
  252. package/dist/server/modules/session-reader/services/transcript-watcher.service.d.ts +28 -0
  253. package/dist/server/modules/session-reader/services/transcript-watcher.service.js +325 -0
  254. package/dist/server/modules/session-reader/services/transcript-watcher.service.js.map +1 -0
  255. package/dist/server/modules/session-reader/session-reader.module.d.ts +13 -0
  256. package/dist/server/modules/session-reader/session-reader.module.js +82 -0
  257. package/dist/server/modules/session-reader/session-reader.module.js.map +1 -0
  258. package/dist/server/modules/sessions/dtos/sessions.dto.d.ts +2 -0
  259. package/dist/server/modules/sessions/dtos/sessions.dto.js.map +1 -1
  260. package/dist/server/modules/sessions/services/sessions-message-pool.service.d.ts +2 -2
  261. package/dist/server/modules/sessions/services/sessions-message-pool.service.js.map +1 -1
  262. package/dist/server/modules/sessions/services/sessions.service.js +19 -4
  263. package/dist/server/modules/sessions/services/sessions.service.js.map +1 -1
  264. package/dist/server/modules/sessions/utils/profile-options.d.ts +1 -0
  265. package/dist/server/modules/sessions/utils/profile-options.js +18 -0
  266. package/dist/server/modules/sessions/utils/profile-options.js.map +1 -1
  267. package/dist/server/modules/settings/controllers/settings.controller.d.ts +2 -2
  268. package/dist/server/modules/settings/controllers/settings.controller.js.map +1 -1
  269. package/dist/server/modules/settings/dtos/settings.dto.d.ts +14 -2
  270. package/dist/server/modules/settings/dtos/settings.dto.js +1 -0
  271. package/dist/server/modules/settings/dtos/settings.dto.js.map +1 -1
  272. package/dist/server/modules/skills/services/skill-source-registry.service.d.ts +2 -2
  273. package/dist/server/modules/skills/services/skill-source-registry.service.js.map +1 -1
  274. package/dist/server/modules/statuses/controllers/statuses.controller.d.ts +2 -2
  275. package/dist/server/modules/statuses/controllers/statuses.controller.js.map +1 -1
  276. package/dist/server/modules/storage/db/schema.d.ts +176 -0
  277. package/dist/server/modules/storage/db/schema.js +17 -1
  278. package/dist/server/modules/storage/db/schema.js.map +1 -1
  279. package/dist/server/modules/storage/interfaces/storage.interface.d.ts +53 -9
  280. package/dist/server/modules/storage/interfaces/storage.interface.js.map +1 -1
  281. package/dist/server/modules/storage/local/delegates/agent-profile.delegate.d.ts +39 -0
  282. package/dist/server/modules/storage/local/delegates/agent-profile.delegate.js +286 -0
  283. package/dist/server/modules/storage/local/delegates/agent-profile.delegate.js.map +1 -0
  284. package/dist/server/modules/storage/local/delegates/agent.delegate.d.ts +21 -0
  285. package/dist/server/modules/storage/local/delegates/agent.delegate.js +210 -0
  286. package/dist/server/modules/storage/local/delegates/agent.delegate.js.map +1 -0
  287. package/dist/server/modules/storage/local/delegates/base-storage.delegate.d.ts +12 -0
  288. package/dist/server/modules/storage/local/delegates/base-storage.delegate.js +19 -0
  289. package/dist/server/modules/storage/local/delegates/base-storage.delegate.js.map +1 -0
  290. package/dist/server/modules/storage/local/delegates/document.delegate.d.ts +20 -0
  291. package/dist/server/modules/storage/local/delegates/document.delegate.js +267 -0
  292. package/dist/server/modules/storage/local/delegates/document.delegate.js.map +1 -0
  293. package/dist/server/modules/storage/local/delegates/epic.delegate.d.ts +38 -0
  294. package/dist/server/modules/storage/local/delegates/epic.delegate.js +686 -0
  295. package/dist/server/modules/storage/local/delegates/epic.delegate.js.map +1 -0
  296. package/dist/server/modules/storage/local/delegates/guest.delegate.d.ts +14 -0
  297. package/dist/server/modules/storage/local/delegates/guest.delegate.js +172 -0
  298. package/dist/server/modules/storage/local/delegates/guest.delegate.js.map +1 -0
  299. package/dist/server/modules/storage/local/delegates/profile-provider-config.delegate.d.ts +17 -0
  300. package/dist/server/modules/storage/local/delegates/profile-provider-config.delegate.js +249 -0
  301. package/dist/server/modules/storage/local/delegates/profile-provider-config.delegate.js.map +1 -0
  302. package/dist/server/modules/storage/local/delegates/project.delegate.d.ts +16 -0
  303. package/dist/server/modules/storage/local/delegates/project.delegate.js +630 -0
  304. package/dist/server/modules/storage/local/delegates/project.delegate.js.map +1 -0
  305. package/dist/server/modules/storage/local/delegates/prompt.delegate.d.ts +17 -0
  306. package/dist/server/modules/storage/local/delegates/prompt.delegate.js +318 -0
  307. package/dist/server/modules/storage/local/delegates/prompt.delegate.js.map +1 -0
  308. package/dist/server/modules/storage/local/delegates/provider-model.delegate.d.ts +15 -0
  309. package/dist/server/modules/storage/local/delegates/provider-model.delegate.js +210 -0
  310. package/dist/server/modules/storage/local/delegates/provider-model.delegate.js.map +1 -0
  311. package/dist/server/modules/storage/local/delegates/provider.delegate.d.ts +18 -0
  312. package/dist/server/modules/storage/local/delegates/provider.delegate.js +151 -0
  313. package/dist/server/modules/storage/local/delegates/provider.delegate.js.map +1 -0
  314. package/dist/server/modules/storage/local/delegates/record.delegate.d.ts +16 -0
  315. package/dist/server/modules/storage/local/delegates/record.delegate.js +190 -0
  316. package/dist/server/modules/storage/local/delegates/record.delegate.js.map +1 -0
  317. package/dist/server/modules/storage/local/delegates/review.delegate.d.ts +25 -0
  318. package/dist/server/modules/storage/local/delegates/review.delegate.js +426 -0
  319. package/dist/server/modules/storage/local/delegates/review.delegate.js.map +1 -0
  320. package/dist/server/modules/storage/local/delegates/skill-source.delegate.d.ts +28 -0
  321. package/dist/server/modules/storage/local/delegates/skill-source.delegate.js +347 -0
  322. package/dist/server/modules/storage/local/delegates/skill-source.delegate.js.map +1 -0
  323. package/dist/server/modules/storage/local/delegates/status.delegate.d.ts +12 -0
  324. package/dist/server/modules/storage/local/delegates/status.delegate.js +130 -0
  325. package/dist/server/modules/storage/local/delegates/status.delegate.js.map +1 -0
  326. package/dist/server/modules/storage/local/delegates/subscriber.delegate.d.ts +11 -0
  327. package/dist/server/modules/storage/local/delegates/subscriber.delegate.js +144 -0
  328. package/dist/server/modules/storage/local/delegates/subscriber.delegate.js.map +1 -0
  329. package/dist/server/modules/storage/local/delegates/tag.delegate.d.ts +11 -0
  330. package/dist/server/modules/storage/local/delegates/tag.delegate.js +102 -0
  331. package/dist/server/modules/storage/local/delegates/tag.delegate.js.map +1 -0
  332. package/dist/server/modules/storage/local/delegates/watcher.delegate.d.ts +12 -0
  333. package/dist/server/modules/storage/local/delegates/watcher.delegate.js +183 -0
  334. package/dist/server/modules/storage/local/delegates/watcher.delegate.js.map +1 -0
  335. package/dist/server/modules/storage/local/helpers/storage-helpers.d.ts +32 -0
  336. package/dist/server/modules/storage/local/helpers/storage-helpers.js +276 -0
  337. package/dist/server/modules/storage/local/helpers/storage-helpers.js.map +1 -0
  338. package/dist/server/modules/storage/local/local-storage.service.d.ts +30 -22
  339. package/dist/server/modules/storage/local/local-storage.service.js +227 -3627
  340. package/dist/server/modules/storage/local/local-storage.service.js.map +1 -1
  341. package/dist/server/modules/storage/models/domain.models.d.ts +14 -1
  342. package/dist/server/modules/subscribers/services/subscribers.service.d.ts +2 -2
  343. package/dist/server/modules/subscribers/services/subscribers.service.js.map +1 -1
  344. package/dist/server/modules/ui/ui.controller.d.ts +2 -2
  345. package/dist/server/modules/ui/ui.controller.js +9 -3
  346. package/dist/server/modules/ui/ui.controller.js.map +1 -1
  347. package/dist/server/modules/watchers/services/watchers.service.d.ts +2 -2
  348. package/dist/server/templates/3-agents-dev.json +209 -191
  349. package/dist/server/templates/5-agents-dev.json +243 -198
  350. package/dist/server/test-setup.js +14 -0
  351. package/dist/server/test-setup.js.map +1 -1
  352. package/dist/server/tsconfig.tsbuildinfo +1 -1
  353. package/dist/server/ui/assets/{ReviewDetailPage-CvHhaFJw.js → ReviewDetailPage-DJyH8bKk.js} +1 -1
  354. package/dist/server/ui/assets/{ReviewsPage-DCDiMjwZ.js → ReviewsPage-DHvmyjjG.js} +1 -1
  355. package/dist/server/ui/assets/index-cHqKW6LQ.css +32 -0
  356. package/dist/server/ui/assets/index-f0KXZKs1.js +1011 -0
  357. package/dist/server/ui/assets/{useReviewSubscription-D_vmMkNu.js → useReviewSubscription-CUbCp0An.js} +32 -35
  358. package/dist/server/ui/index.html +2 -2
  359. package/dist/templates/3-agents-dev.json +209 -191
  360. package/dist/templates/5-agents-dev.json +243 -198
  361. package/package.json +18 -1
  362. package/dist/server/ui/assets/index-CWqSXDUZ.js +0 -977
  363. package/dist/server/ui/assets/index-DvRuLfpZ.css +0 -32
@@ -1,43 +1,10 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
2
  var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
19
3
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
20
4
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
21
5
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
22
6
  return c > 3 && r && Object.defineProperty(target, key, r), r;
23
7
  };
24
- var __importStar = (this && this.__importStar) || (function () {
25
- var ownKeys = function(o) {
26
- ownKeys = Object.getOwnPropertyNames || function (o) {
27
- var ar = [];
28
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
- return ar;
30
- };
31
- return ownKeys(o);
32
- };
33
- return function (mod) {
34
- if (mod && mod.__esModule) return mod;
35
- var result = {};
36
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
- __setModuleDefault(result, mod);
38
- return result;
39
- };
40
- })();
41
8
  var __metadata = (this && this.__metadata) || function (k, v) {
42
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
43
10
  };
@@ -59,28 +26,22 @@ const guests_service_1 = require("../../guests/services/guests.service");
59
26
  const reviews_service_1 = require("../../reviews/services/reviews.service");
60
27
  const skills_service_1 = require("../../skills/services/skills.service");
61
28
  const logger_1 = require("../../../common/logging/logger");
62
- const schema_registry_1 = require("../dtos/schema-registry");
63
- const mcp_dto_1 = require("../dtos/mcp.dto");
64
29
  const instructions_resolver_1 = require("./instructions-resolver");
65
- const error_types_1 = require("../../../common/errors/error-types");
66
- const path_validation_1 = require("../../../common/validation/path-validation");
67
- const common_2 = require("@nestjs/common");
68
30
  const zod_1 = require("zod");
31
+ const document_link_resolver_1 = require("./utils/document-link-resolver");
32
+ const epic_tools_1 = require("./handlers/epic-tools");
33
+ const chat_tools_1 = require("./handlers/chat-tools");
34
+ const activity_tools_1 = require("./handlers/activity-tools");
35
+ const record_tools_1 = require("./handlers/record-tools");
36
+ const document_tools_1 = require("./handlers/document-tools");
37
+ const prompt_tools_1 = require("./handlers/prompt-tools");
38
+ const skill_tools_1 = require("./handlers/skill-tools");
39
+ const session_tools_1 = require("./handlers/session-tools");
40
+ const review_tools_1 = require("./handlers/review-tools");
69
41
  const param_suggestion_1 = require("../utils/param-suggestion");
42
+ const session_context_resolver_1 = require("./utils/session-context-resolver");
43
+ const resource_resolver_1 = require("./utils/resource-resolver");
70
44
  const logger = (0, logger_1.createLogger)('McpService');
71
- function getActorFromContext(ctx) {
72
- if (ctx.type === 'agent') {
73
- return ctx.agent;
74
- }
75
- else if (ctx.type === 'guest') {
76
- return {
77
- id: ctx.guest.id,
78
- name: ctx.guest.name,
79
- projectId: ctx.guest.projectId,
80
- };
81
- }
82
- return null;
83
- }
84
45
  function redactSessionId(sessionId) {
85
46
  if (!sessionId)
86
47
  return '(none)';
@@ -95,6 +56,45 @@ function redactParams(params) {
95
56
  }
96
57
  return params;
97
58
  }
59
+ const TOOL_HANDLER_ENTRIES = [
60
+ ['devchain_create_record', record_tools_1.handleCreateRecord],
61
+ ['devchain_update_record', record_tools_1.handleUpdateRecord],
62
+ ['devchain_get_record', record_tools_1.handleGetRecord],
63
+ ['devchain_list_records', record_tools_1.handleListRecords],
64
+ ['devchain_add_tags', record_tools_1.handleAddTags],
65
+ ['devchain_remove_tags', record_tools_1.handleRemoveTags],
66
+ ['devchain_list_documents', document_tools_1.handleListDocuments],
67
+ ['devchain_get_document', document_tools_1.handleGetDocument],
68
+ ['devchain_create_document', document_tools_1.handleCreateDocument],
69
+ ['devchain_update_document', document_tools_1.handleUpdateDocument],
70
+ ['devchain_list_prompts', prompt_tools_1.handleListPrompts],
71
+ ['devchain_get_prompt', prompt_tools_1.handleGetPrompt],
72
+ ['devchain_list_skills', skill_tools_1.handleListSkills],
73
+ ['devchain_get_skill', skill_tools_1.handleGetSkill],
74
+ ['devchain_list_agents', epic_tools_1.handleListAgents],
75
+ ['devchain_get_agent_by_name', epic_tools_1.handleGetAgentByName],
76
+ ['devchain_list_statuses', epic_tools_1.handleListStatuses],
77
+ ['devchain_list_epics', epic_tools_1.handleListEpics],
78
+ ['devchain_list_assigned_epics_tasks', epic_tools_1.handleListAssignedEpicsTasks],
79
+ ['devchain_create_epic', epic_tools_1.handleCreateEpic],
80
+ ['devchain_get_epic_by_id', epic_tools_1.handleGetEpicById],
81
+ ['devchain_add_epic_comment', epic_tools_1.handleAddEpicComment],
82
+ ['devchain_update_epic', epic_tools_1.handleUpdateEpic],
83
+ ['devchain_send_message', chat_tools_1.handleSendMessage],
84
+ ['devchain_chat_ack', chat_tools_1.handleChatAck],
85
+ ['devchain_chat_list_members', chat_tools_1.handleChatListMembers],
86
+ ['devchain_chat_read_history', chat_tools_1.handleChatReadHistory],
87
+ ['devchain_activity_start', activity_tools_1.handleActivityStart],
88
+ ['devchain_activity_finish', activity_tools_1.handleActivityFinish],
89
+ ['devchain_list_sessions', session_tools_1.handleListSessions],
90
+ ['devchain_register_guest', session_tools_1.handleRegisterGuest],
91
+ ['devchain_list_reviews', review_tools_1.handleListReviews],
92
+ ['devchain_get_review', review_tools_1.handleGetReview],
93
+ ['devchain_get_review_comments', review_tools_1.handleGetReviewComments],
94
+ ['devchain_reply_comment', review_tools_1.handleReplyComment],
95
+ ['devchain_resolve_comment', review_tools_1.handleResolveComment],
96
+ ['devchain_apply_suggestion', review_tools_1.handleApplySuggestion],
97
+ ];
98
98
  let McpService = class McpService {
99
99
  constructor(storage, chatService, sessionsService, messagePoolService, terminalGateway, tmuxService, epicsService, settingsService, guestsService, skillsService, reviewsService) {
100
100
  this.storage = storage;
@@ -111,141 +111,63 @@ let McpService = class McpService {
111
111
  this.DEFAULT_INLINE_MAX_BYTES = 64 * 1024;
112
112
  logger.info('McpService initialized');
113
113
  this.featureFlags = this.storage.getFeatureFlags();
114
- this.instructionsResolver = new instructions_resolver_1.InstructionsResolver(this.storage, (document, cache, maxDepth, maxBytes) => this.buildInlineResolution(document, cache, maxDepth, maxBytes), this.featureFlags);
114
+ this.instructionsResolver = new instructions_resolver_1.InstructionsResolver(this.storage, (document, cache, maxDepth, maxBytes) => (0, document_link_resolver_1.buildInlineResolution)(this.storage, document, cache, maxDepth, maxBytes), this.featureFlags);
115
+ this.sessionContextResolver = new session_context_resolver_1.SessionContextResolver(this.storage, this.sessionsService, this.guestsService, this.tmuxService);
116
+ this.resourceResolver = new resource_resolver_1.ResourceResolver(this.storage);
117
+ this.toolHandlers = new Map(TOOL_HANDLER_ENTRIES);
115
118
  }
116
- async resolveAgentByNameUnique(projectId, name) {
117
- const normalized = name.trim().toLowerCase();
118
- const list = await this.storage.listAgents(projectId, { limit: 1000, offset: 0 });
119
- const matches = list.items.filter((a) => a.name.toLowerCase() === normalized);
120
- if (matches.length === 0) {
121
- return {
122
- success: false,
123
- error: {
124
- code: 'AGENT_NOT_FOUND',
125
- message: `Agent "${name}" not found in project`,
126
- data: { availableNames: list.items.map((a) => a.name) },
127
- },
128
- };
129
- }
130
- if (matches.length > 1) {
131
- return {
132
- success: false,
133
- error: {
134
- code: 'AMBIGUOUS_AGENT_NAME',
135
- message: `Multiple agents named "${name}" found in project; please disambiguate`,
136
- data: { matches: matches.map((a) => ({ id: a.id, name: a.name })) },
137
- },
138
- };
139
- }
140
- return { id: matches[0].id, name: matches[0].name };
119
+ buildHandlerContext() {
120
+ return {
121
+ storage: this.storage,
122
+ chatService: this.chatService,
123
+ sessionsService: this.sessionsService,
124
+ messagePoolService: this.messagePoolService,
125
+ terminalGateway: this.terminalGateway,
126
+ tmuxService: this.tmuxService,
127
+ epicsService: this.epicsService,
128
+ settingsService: this.settingsService,
129
+ guestsService: this.guestsService,
130
+ skillsService: this.skillsService,
131
+ reviewsService: this.reviewsService,
132
+ instructionsResolver: this.instructionsResolver,
133
+ featureFlags: this.featureFlags,
134
+ defaultInlineMaxBytes: this.DEFAULT_INLINE_MAX_BYTES,
135
+ resolveSessionContext: (sessionId) => this.resolveSessionContext(sessionId),
136
+ };
141
137
  }
142
138
  async handleToolCall(tool, params) {
143
139
  const normalizedParams = params ?? {};
144
140
  const normalizedTool = tool.replace(/[.\-/]/g, '_');
145
141
  try {
146
142
  logger.info({ tool: normalizedTool, originalTool: tool, params: redactParams(normalizedParams) }, 'Handling MCP tool call');
147
- switch (normalizedTool) {
148
- case 'devchain_create_record':
149
- return await this.createRecord(normalizedParams);
150
- case 'devchain_update_record':
151
- return await this.updateRecord(normalizedParams);
152
- case 'devchain_get_record':
153
- return await this.getRecord(normalizedParams);
154
- case 'devchain_list_records':
155
- return await this.listRecords(normalizedParams);
156
- case 'devchain_add_tags':
157
- return await this.addTags(normalizedParams);
158
- case 'devchain_remove_tags':
159
- return await this.removeTags(normalizedParams);
160
- case 'devchain_list_documents':
161
- return await this.listDocuments(normalizedParams);
162
- case 'devchain_get_document':
163
- return await this.getDocument(normalizedParams);
164
- case 'devchain_create_document':
165
- return await this.createDocument(normalizedParams);
166
- case 'devchain_update_document':
167
- return await this.updateDocument(normalizedParams);
168
- case 'devchain_list_prompts':
169
- return await this.listPrompts(normalizedParams);
170
- case 'devchain_get_prompt':
171
- return await this.getPrompt(normalizedParams);
172
- case 'devchain_list_skills':
173
- return await this.listSkills(normalizedParams);
174
- case 'devchain_get_skill':
175
- return await this.getSkill(normalizedParams);
176
- case 'devchain_list_agents':
177
- return await this.listAgents(normalizedParams);
178
- case 'devchain_get_agent_by_name':
179
- return await this.getAgentByName(normalizedParams);
180
- case 'devchain_list_statuses':
181
- return await this.listStatuses(normalizedParams);
182
- case 'devchain_list_epics':
183
- return await this.listEpics(normalizedParams);
184
- case 'devchain_list_assigned_epics_tasks':
185
- return await this.listAssignedEpicsTasks(normalizedParams);
186
- case 'devchain_create_epic':
187
- return await this.createEpic(normalizedParams);
188
- case 'devchain_get_epic_by_id':
189
- return await this.getEpicById(normalizedParams);
190
- case 'devchain_add_epic_comment':
191
- return await this.addEpicComment(normalizedParams);
192
- case 'devchain_update_epic':
193
- return await this.updateEpic(normalizedParams);
194
- case 'notifications_initialized':
195
- return { success: true, data: { acknowledged: true } };
196
- case 'devchain_send_message':
197
- return await this.sendMessage(normalizedParams);
198
- case 'devchain_chat_ack':
199
- return await this.chatAck(normalizedParams);
200
- case 'devchain_chat_list_members':
201
- return await this.chatListMembers(normalizedParams);
202
- case 'devchain_chat_read_history':
203
- return await this.chatReadHistory(normalizedParams);
204
- case 'devchain_activity_start':
205
- return await this.activityStart(normalizedParams);
206
- case 'devchain_activity_finish':
207
- return await this.activityFinish(normalizedParams);
208
- case 'devchain_list_sessions':
209
- schema_registry_1.ListSessionsParamsSchema.parse(normalizedParams);
210
- return await this.listSessions();
211
- case 'devchain_register_guest':
212
- return await this.registerGuest(normalizedParams);
213
- case 'devchain_list_reviews':
214
- return await this.listReviews(normalizedParams);
215
- case 'devchain_get_review':
216
- return await this.getReview(normalizedParams);
217
- case 'devchain_get_review_comments':
218
- return await this.getReviewComments(normalizedParams);
219
- case 'devchain_reply_comment':
220
- return await this.replyComment(normalizedParams);
221
- case 'devchain_resolve_comment':
222
- return await this.resolveComment(normalizedParams);
223
- case 'devchain_apply_suggestion':
224
- return await this.applySuggestion(normalizedParams);
225
- default:
226
- logger.warn({ tool: normalizedTool }, 'Unknown MCP tool');
227
- return {
228
- success: false,
229
- error: {
230
- code: 'UNKNOWN_TOOL',
231
- message: `Unknown tool: ${tool}`,
232
- },
233
- };
143
+ if (normalizedTool === 'notifications_initialized') {
144
+ return { success: true, data: { acknowledged: true } };
145
+ }
146
+ const handler = this.toolHandlers.get(normalizedTool);
147
+ if (!handler) {
148
+ logger.warn({ tool: normalizedTool }, 'Unknown MCP tool');
149
+ return {
150
+ success: false,
151
+ error: {
152
+ code: 'UNKNOWN_TOOL',
153
+ message: `Unknown tool: ${tool}`,
154
+ },
155
+ };
234
156
  }
157
+ return await handler(this.buildHandlerContext(), normalizedParams);
235
158
  }
236
159
  catch (error) {
237
160
  logger.error({ tool, error }, 'MCP tool call failed');
238
161
  if (error instanceof zod_1.ZodError) {
239
162
  const suggestions = [];
240
163
  for (const issue of error.issues) {
241
- if (issue.code === 'unrecognized_keys') {
242
- const unknownKeys = issue.keys;
243
- for (const key of unknownKeys) {
244
- const suggestion = (0, param_suggestion_1.suggestNestedPath)(key, normalizedTool);
245
- if (suggestion) {
246
- suggestions.push(suggestion);
247
- }
248
- }
164
+ if (issue.code !== 'unrecognized_keys')
165
+ continue;
166
+ const unknownKeys = issue.keys;
167
+ for (const key of unknownKeys) {
168
+ const suggestion = (0, param_suggestion_1.suggestNestedPath)(key, normalizedTool);
169
+ if (suggestion)
170
+ suggestions.push(suggestion);
249
171
  }
250
172
  }
251
173
  return {
@@ -272,19 +194,7 @@ let McpService = class McpService {
272
194
  async handleResourceRequest(uri) {
273
195
  try {
274
196
  logger.info({ uri }, 'Handling MCP resource request');
275
- if (uri.startsWith('doc://')) {
276
- return await this.resolveDocumentResource(uri);
277
- }
278
- if (uri.startsWith('prompt://')) {
279
- return await this.resolvePromptResource(uri);
280
- }
281
- return {
282
- success: false,
283
- error: {
284
- code: 'UNKNOWN_RESOURCE',
285
- message: `Unknown resource: ${uri}`,
286
- },
287
- };
197
+ return await this.resourceResolver.resolve(uri);
288
198
  }
289
199
  catch (error) {
290
200
  logger.error({ uri, error }, 'MCP resource handler failed');
@@ -297,3150 +207,8 @@ let McpService = class McpService {
297
207
  };
298
208
  }
299
209
  }
300
- async createRecord(params) {
301
- const validated = mcp_dto_1.CreateRecordParamsSchema.parse(params);
302
- const record = await this.storage.createRecord({
303
- epicId: validated.epicId,
304
- type: validated.type,
305
- data: validated.data,
306
- tags: validated.tags || [],
307
- });
308
- const response = {
309
- id: record.id,
310
- epicId: record.epicId,
311
- type: record.type,
312
- data: record.data,
313
- tags: record.tags,
314
- version: record.version,
315
- createdAt: record.createdAt,
316
- updatedAt: record.updatedAt,
317
- };
318
- return { success: true, data: response };
319
- }
320
- async updateRecord(params) {
321
- const validated = mcp_dto_1.UpdateRecordParamsSchema.parse(params);
322
- const record = await this.storage.updateRecord(validated.id, {
323
- data: validated.data,
324
- type: validated.type,
325
- tags: validated.tags,
326
- }, validated.version);
327
- const response = {
328
- id: record.id,
329
- epicId: record.epicId,
330
- type: record.type,
331
- data: record.data,
332
- tags: record.tags,
333
- version: record.version,
334
- createdAt: record.createdAt,
335
- updatedAt: record.updatedAt,
336
- };
337
- return { success: true, data: response };
338
- }
339
- async getRecord(params) {
340
- const validated = mcp_dto_1.GetRecordParamsSchema.parse(params);
341
- const record = await this.storage.getRecord(validated.id);
342
- const response = {
343
- id: record.id,
344
- epicId: record.epicId,
345
- type: record.type,
346
- data: record.data,
347
- tags: record.tags,
348
- version: record.version,
349
- createdAt: record.createdAt,
350
- updatedAt: record.updatedAt,
351
- };
352
- return { success: true, data: response };
353
- }
354
- async listRecords(params) {
355
- const validated = mcp_dto_1.ListRecordsParamsSchema.parse(params);
356
- const result = await this.storage.listRecords(validated.epicId, {
357
- limit: validated.limit,
358
- offset: validated.offset,
359
- });
360
- let filtered = result.items;
361
- if (validated.type) {
362
- filtered = filtered.filter((r) => r.type === validated.type);
363
- }
364
- if (validated.tags && validated.tags.length > 0) {
365
- filtered = filtered.filter((r) => {
366
- return validated.tags.every((tag) => r.tags.includes(tag));
367
- });
368
- }
369
- const response = {
370
- records: filtered.map((r) => ({
371
- id: r.id,
372
- epicId: r.epicId,
373
- type: r.type,
374
- data: r.data,
375
- tags: r.tags,
376
- version: r.version,
377
- createdAt: r.createdAt,
378
- updatedAt: r.updatedAt,
379
- })),
380
- total: filtered.length,
381
- };
382
- return { success: true, data: response };
383
- }
384
- async addTags(params) {
385
- const validated = mcp_dto_1.AddTagsParamsSchema.parse(params);
386
- const record = await this.storage.getRecord(validated.id);
387
- const newTags = Array.from(new Set([...record.tags, ...validated.tags]));
388
- const updated = await this.storage.updateRecord(validated.id, { tags: newTags }, record.version);
389
- const response = {
390
- id: updated.id,
391
- tags: updated.tags,
392
- };
393
- return { success: true, data: response };
394
- }
395
- async removeTags(params) {
396
- const validated = mcp_dto_1.RemoveTagsParamsSchema.parse(params);
397
- const record = await this.storage.getRecord(validated.id);
398
- const newTags = record.tags.filter((tag) => !validated.tags.includes(tag));
399
- const updated = await this.storage.updateRecord(validated.id, { tags: newTags }, record.version);
400
- const response = {
401
- id: updated.id,
402
- tags: updated.tags,
403
- };
404
- return { success: true, data: response };
405
- }
406
- async listDocuments(params) {
407
- const validated = mcp_dto_1.ListDocumentsParamsSchema.parse(params);
408
- const ctx = await this.resolveSessionContext(validated.sessionId);
409
- if (!ctx.success)
410
- return ctx;
411
- const { project } = ctx.data;
412
- if (!project) {
413
- return {
414
- success: false,
415
- error: {
416
- code: 'PROJECT_NOT_FOUND',
417
- message: 'No project associated with this session',
418
- },
419
- };
420
- }
421
- logger.debug({ sessionId: redactSessionId(validated.sessionId), projectId: project.id }, 'Resolved session to project');
422
- const filters = {
423
- projectId: project.id,
424
- };
425
- if (validated.tags?.length) {
426
- filters.tags = validated.tags;
427
- }
428
- if (validated.q) {
429
- filters.q = validated.q;
430
- }
431
- if (validated.limit !== undefined) {
432
- filters.limit = validated.limit;
433
- }
434
- if (validated.offset !== undefined) {
435
- filters.offset = validated.offset;
436
- }
437
- const result = await this.storage.listDocuments(filters);
438
- const response = {
439
- documents: result.items.map((doc) => this.mapDocumentSummary(doc)),
440
- total: result.total,
441
- limit: result.limit,
442
- offset: result.offset,
443
- };
444
- return { success: true, data: response };
445
- }
446
- async getDocument(params) {
447
- const validated = mcp_dto_1.GetDocumentParamsSchema.parse(params);
448
- const includeLinks = validated.includeLinks ?? 'meta';
449
- let document;
450
- if (validated.id) {
451
- document = await this.storage.getDocument({ id: validated.id });
452
- }
453
- else {
454
- const projectId = validated.projectId === '' ? null : validated.projectId;
455
- document = await this.storage.getDocument({ slug: validated.slug, projectId });
456
- }
457
- const response = {
458
- document: this.mapDocumentDetail(document),
459
- links: [],
460
- };
461
- let cache = new Map();
462
- if (includeLinks !== 'none') {
463
- const collected = await this.collectDocumentLinks(document);
464
- response.links = collected.links;
465
- cache = collected.cache;
466
- if (includeLinks === 'inline') {
467
- const inline = await this.buildInlineResolution(document, cache, validated.maxDepth ?? 1, validated.maxBytes ?? this.DEFAULT_INLINE_MAX_BYTES);
468
- response.resolved = inline;
469
- }
470
- }
471
- return { success: true, data: response };
472
- }
473
- async createDocument(params) {
474
- const validated = mcp_dto_1.CreateDocumentParamsSchema.parse(params);
475
- const ctx = await this.resolveSessionContext(validated.sessionId);
476
- if (!ctx.success)
477
- return ctx;
478
- const { project } = ctx.data;
479
- if (!project) {
480
- return {
481
- success: false,
482
- error: {
483
- code: 'PROJECT_NOT_FOUND',
484
- message: 'No project associated with this session',
485
- },
486
- };
487
- }
488
- logger.debug({ sessionId: redactSessionId(validated.sessionId), projectId: project.id }, 'Resolved session to project for document creation');
489
- const document = await this.storage.createDocument({
490
- projectId: project.id,
491
- title: validated.title,
492
- contentMd: validated.contentMd,
493
- tags: validated.tags,
494
- });
495
- const response = {
496
- document: this.mapDocumentDetail(document),
497
- };
498
- return { success: true, data: response };
499
- }
500
- async updateDocument(params) {
501
- const validated = mcp_dto_1.UpdateDocumentParamsSchema.parse(params);
502
- const document = await this.storage.updateDocument(validated.id, {
503
- title: validated.title,
504
- slug: validated.slug,
505
- contentMd: validated.contentMd,
506
- tags: validated.tags,
507
- archived: validated.archived,
508
- version: validated.version,
509
- });
510
- const response = {
511
- document: this.mapDocumentDetail(document),
512
- };
513
- return { success: true, data: response };
514
- }
515
- async listPrompts(params) {
516
- const validated = mcp_dto_1.ListPromptsParamsSchema.parse(params);
517
- const ctx = await this.resolveSessionContext(validated.sessionId);
518
- if (!ctx.success)
519
- return ctx;
520
- const { project } = ctx.data;
521
- if (!project) {
522
- return {
523
- success: false,
524
- error: {
525
- code: 'PROJECT_NOT_FOUND',
526
- message: 'No project associated with this session',
527
- },
528
- };
529
- }
530
- const projectId = project.id;
531
- const result = await this.storage.listPrompts({
532
- projectId: projectId ?? null,
533
- q: validated.q,
534
- });
535
- let items = result.items;
536
- if (validated.tags?.length) {
537
- items = items.filter((prompt) => validated.tags.every((tag) => prompt.tags.includes(tag)));
538
- }
539
- const response = {
540
- prompts: items.map((prompt) => this.mapPromptSummary(prompt)),
541
- total: items.length,
542
- };
543
- return { success: true, data: response };
544
- }
545
- async getPrompt(params) {
546
- const validated = mcp_dto_1.GetPromptParamsSchema.parse(params);
547
- let prompt;
548
- if (validated.id) {
549
- prompt = await this.storage.getPrompt(validated.id);
550
- }
551
- else if (validated.name) {
552
- if (!validated.sessionId) {
553
- return {
554
- success: false,
555
- error: {
556
- code: 'INVALID_PARAMS',
557
- message: 'sessionId is required when querying prompt by name',
558
- },
559
- };
560
- }
561
- const ctx = await this.resolveSessionContext(validated.sessionId);
562
- if (!ctx.success)
563
- return ctx;
564
- const { project } = ctx.data;
565
- if (!project) {
566
- return {
567
- success: false,
568
- error: { code: 'PROJECT_NOT_FOUND', message: 'No project associated with this session' },
569
- };
570
- }
571
- const projectId = project.id;
572
- const list = await this.storage.listPrompts({ projectId: projectId ?? null });
573
- const found = list.items.find((item) => {
574
- if (item.title !== validated.name) {
575
- return false;
576
- }
577
- if (validated.version !== undefined) {
578
- return item.version === validated.version;
579
- }
580
- return true;
581
- });
582
- if (found) {
583
- prompt = await this.storage.getPrompt(found.id);
584
- }
585
- }
586
- if (!prompt) {
587
- return {
588
- success: false,
589
- error: {
590
- code: 'PROMPT_NOT_FOUND',
591
- message: validated.id
592
- ? `Prompt with id "${validated.id}" not found`
593
- : `Prompt "${validated.name}"${validated.version ? ` version ${validated.version}` : ''} not found`,
594
- },
595
- };
596
- }
597
- const response = {
598
- prompt: this.mapPromptDetail(prompt),
599
- };
600
- return { success: true, data: response };
601
- }
602
- async listSkills(params) {
603
- if (!this.skillsService) {
604
- return {
605
- success: false,
606
- error: {
607
- code: 'SERVICE_UNAVAILABLE',
608
- message: 'Skill listing requires SkillsService to be available',
609
- },
610
- };
611
- }
612
- const validated = mcp_dto_1.ListSkillsParamsSchema.parse(params);
613
- const ctx = await this.resolveSessionContext(validated.sessionId);
614
- if (!ctx.success)
615
- return ctx;
616
- const { project } = ctx.data;
617
- if (!project) {
618
- return {
619
- success: false,
620
- error: {
621
- code: 'PROJECT_NOT_FOUND',
622
- message: 'No project associated with this session',
623
- },
624
- };
625
- }
626
- const projectSkills = await this.skillsService.listDiscoverable(project.id, { q: validated.q });
627
- const response = {
628
- skills: projectSkills.map((skill) => this.mapSkillListItem(skill)),
629
- total: projectSkills.length,
630
- };
631
- return { success: true, data: response };
632
- }
633
- async getSkill(params) {
634
- if (!this.skillsService) {
635
- return {
636
- success: false,
637
- error: {
638
- code: 'SERVICE_UNAVAILABLE',
639
- message: 'Skill retrieval requires SkillsService to be available',
640
- },
641
- };
642
- }
643
- const validated = mcp_dto_1.GetSkillParamsSchema.parse(params);
644
- const ctx = await this.resolveSessionContext(validated.sessionId);
645
- if (!ctx.success)
646
- return ctx;
647
- const sessionCtx = ctx.data;
648
- const { project } = sessionCtx;
649
- if (!project) {
650
- return {
651
- success: false,
652
- error: {
653
- code: 'PROJECT_NOT_FOUND',
654
- message: 'No project associated with this session',
655
- },
656
- };
657
- }
658
- const normalizedSlug = validated.slug.trim().toLowerCase();
659
- let skill;
660
- try {
661
- skill = await this.skillsService.getSkillBySlug(normalizedSlug);
662
- }
663
- catch (error) {
664
- if (error instanceof error_types_1.NotFoundError) {
665
- return {
666
- success: false,
667
- error: {
668
- code: 'SKILL_NOT_FOUND',
669
- message: `Skill "${validated.slug}" was not found.`,
670
- },
671
- };
672
- }
673
- if (error instanceof error_types_1.ValidationError) {
674
- return {
675
- success: false,
676
- error: {
677
- code: 'VALIDATION_ERROR',
678
- message: error.message,
679
- data: error.details,
680
- },
681
- };
682
- }
683
- throw error;
684
- }
685
- const actor = getActorFromContext(sessionCtx);
686
- await this.skillsService.logUsage(skill.id, skill.slug, project.id, actor?.id ?? null, actor?.name ?? null);
687
- const response = this.mapSkillDetail(skill);
688
- return { success: true, data: response };
689
- }
690
- async listAgents(params) {
691
- const validated = mcp_dto_1.ListAgentsParamsSchema.parse(params);
692
- const ctx = await this.resolveSessionContext(validated.sessionId);
693
- if (!ctx.success)
694
- return ctx;
695
- const { project } = ctx.data;
696
- if (!project) {
697
- return {
698
- success: false,
699
- error: {
700
- code: 'PROJECT_NOT_FOUND',
701
- message: 'No project associated with this session',
702
- },
703
- };
704
- }
705
- const limit = validated.limit ?? 100;
706
- const offset = validated.offset ?? 0;
707
- const normalizedQuery = validated.q?.toLowerCase();
708
- const MAX_COMBINED_FETCH = 1000;
709
- const [agentsResult, guests] = await Promise.all([
710
- this.storage.listAgents(project.id, { limit: MAX_COMBINED_FETCH, offset: 0 }),
711
- this.storage.listGuests(project.id),
712
- ]);
713
- const [agentPresence, tmuxSessions] = await Promise.all([
714
- this.sessionsService
715
- ? this.sessionsService.getAgentPresence(project.id)
716
- : Promise.resolve(new Map()),
717
- this.tmuxService
718
- ? this.tmuxService.listAllSessionNames()
719
- : Promise.resolve(new Set()),
720
- ]);
721
- const agentItems = agentsResult.items.map((agent) => ({
722
- id: agent.id,
723
- name: agent.name,
724
- profileId: agent.profileId,
725
- description: agent.description,
726
- type: 'agent',
727
- online: agentPresence.get(agent.id)?.online ?? false,
728
- }));
729
- const guestItems = guests.map((guest) => ({
730
- id: guest.id,
731
- name: guest.name,
732
- profileId: null,
733
- description: guest.description,
734
- type: 'guest',
735
- online: tmuxSessions.has(guest.tmuxSessionId),
736
- }));
737
- let allItems = [...agentItems, ...guestItems].sort((a, b) => {
738
- const nameCompare = a.name.localeCompare(b.name);
739
- if (nameCompare !== 0)
740
- return nameCompare;
741
- return a.type === 'agent' ? -1 : 1;
742
- });
743
- if (normalizedQuery) {
744
- allItems = allItems.filter((item) => item.name.toLowerCase().includes(normalizedQuery));
745
- }
746
- const total = allItems.length;
747
- const paginatedItems = allItems.slice(offset, offset + limit);
748
- const response = {
749
- agents: paginatedItems,
750
- total,
751
- limit,
752
- offset,
753
- };
754
- return { success: true, data: response };
755
- }
756
- async getAgentByName(params) {
757
- const validated = mcp_dto_1.GetAgentByNameParamsSchema.parse(params);
758
- const ctx = await this.resolveSessionContext(validated.sessionId);
759
- if (!ctx.success)
760
- return ctx;
761
- const { project } = ctx.data;
762
- if (!project) {
763
- return {
764
- success: false,
765
- error: {
766
- code: 'PROJECT_NOT_FOUND',
767
- message: 'No project associated with this session',
768
- },
769
- };
770
- }
771
- const normalizedName = validated.name.trim().toLowerCase();
772
- const agentsList = await this.storage.listAgents(project.id, { limit: 1000, offset: 0 });
773
- const candidate = agentsList.items.find((agent) => agent.name.toLowerCase() === normalizedName);
774
- if (!candidate) {
775
- return {
776
- success: false,
777
- error: {
778
- code: 'AGENT_NOT_FOUND',
779
- message: `Agent "${validated.name}" not found in project`,
780
- data: {
781
- availableNames: agentsList.items.map((agent) => agent.name),
782
- },
783
- },
784
- };
785
- }
786
- let agentWithProfile;
787
- try {
788
- agentWithProfile = await this.storage.getAgentByName(project.id, candidate.name);
789
- }
790
- catch (error) {
791
- if (error instanceof error_types_1.NotFoundError) {
792
- return {
793
- success: false,
794
- error: {
795
- code: 'AGENT_NOT_FOUND',
796
- message: `Agent "${validated.name}" not found in project`,
797
- data: {
798
- availableNames: agentsList.items.map((agent) => agent.name),
799
- },
800
- },
801
- };
802
- }
803
- logger.warn({ projectId: project.id, name: candidate.name, error }, 'Agent lookup failed after matching by name');
804
- throw error;
805
- }
806
- const profile = agentWithProfile.profile;
807
- const resolvedInstructions = profile
808
- ? await this.instructionsResolver.resolve(project.id, profile.instructions ?? null, {
809
- maxBytes: this.DEFAULT_INLINE_MAX_BYTES,
810
- })
811
- : null;
812
- if (profile && this.featureFlags.enableProfileInstructionTemplates) {
813
- }
814
- const response = {
815
- agent: {
816
- id: agentWithProfile.id,
817
- name: agentWithProfile.name,
818
- profileId: agentWithProfile.profileId,
819
- description: agentWithProfile.description,
820
- profile: profile
821
- ? {
822
- id: profile.id,
823
- name: profile.name,
824
- instructions: profile.instructions ?? null,
825
- instructionsResolved: resolvedInstructions ?? undefined,
826
- }
827
- : undefined,
828
- },
829
- };
830
- return { success: true, data: response };
831
- }
832
- async listStatuses(params) {
833
- const validated = mcp_dto_1.ListStatusesParamsSchema.parse(params);
834
- const ctx = await this.resolveSessionContext(validated.sessionId);
835
- if (!ctx.success)
836
- return ctx;
837
- const { project } = ctx.data;
838
- if (!project) {
839
- return {
840
- success: false,
841
- error: {
842
- code: 'PROJECT_NOT_FOUND',
843
- message: 'No project associated with this session',
844
- },
845
- };
846
- }
847
- const result = await this.storage.listStatuses(project.id, {
848
- limit: 1000,
849
- offset: 0,
850
- });
851
- const response = {
852
- statuses: result.items.map((status) => this.mapStatusSummary(status)),
853
- };
854
- return { success: true, data: response };
855
- }
856
- async listEpics(params) {
857
- const validated = mcp_dto_1.ListEpicsParamsSchema.parse(params);
858
- const ctx = await this.resolveSessionContext(validated.sessionId);
859
- if (!ctx.success)
860
- return ctx;
861
- const { project } = ctx.data;
862
- if (!project) {
863
- return {
864
- success: false,
865
- error: {
866
- code: 'PROJECT_NOT_FOUND',
867
- message: 'No project associated with this session',
868
- },
869
- };
870
- }
871
- let statusId;
872
- if (validated.statusName) {
873
- const status = await this.storage.findStatusByName(project.id, validated.statusName);
874
- if (!status) {
875
- return {
876
- success: false,
877
- error: {
878
- code: 'STATUS_NOT_FOUND',
879
- message: `Status "${validated.statusName}" was not found for project ${project.id}.`,
880
- },
881
- };
882
- }
883
- statusId = status.id;
884
- }
885
- const limit = validated.limit ?? 100;
886
- const offset = validated.offset ?? 0;
887
- const query = validated.q?.trim();
888
- const result = await this.storage.listProjectEpics(project.id, {
889
- statusId,
890
- q: query && query.length ? query : undefined,
891
- limit,
892
- offset,
893
- excludeMcpHidden: true,
894
- parentOnly: true,
895
- });
896
- const statusesResult = await this.storage.listStatuses(project.id, {
897
- limit: 1000,
898
- offset: 0,
899
- });
900
- const statusById = new Map();
901
- for (const s of statusesResult.items)
902
- statusById.set(s.id, s);
903
- const agentIds = new Set();
904
- for (const epic of result.items) {
905
- if (epic.agentId)
906
- agentIds.add(epic.agentId);
907
- }
908
- const agentNameById = new Map();
909
- for (const agentId of agentIds) {
910
- try {
911
- const agent = await this.storage.getAgent(agentId);
912
- agentNameById.set(agentId, agent.name);
913
- }
914
- catch (error) {
915
- logger.warn({ agentId }, 'Failed to resolve agent name');
916
- }
917
- }
918
- const parentIds = result.items.map((epic) => epic.id);
919
- const subEpicsMap = await this.storage.listSubEpicsForParents(project.id, parentIds, {
920
- excludeMcpHidden: true,
921
- type: 'active',
922
- limitPerParent: 50,
923
- });
924
- const epicsWithStatus = result.items.map((epic) => {
925
- const summary = this.mapEpicSummary(epic, agentNameById);
926
- const s = statusById.get(epic.statusId);
927
- if (s) {
928
- summary.status = this.mapStatusSummary(s);
929
- }
930
- const subEpics = subEpicsMap.get(epic.id) ?? [];
931
- summary.subEpics = subEpics.map((subEpic) => {
932
- const child = this.mapEpicChild(subEpic);
933
- const subStatus = statusById.get(subEpic.statusId);
934
- if (subStatus) {
935
- child.status = this.mapStatusSummary(subStatus);
936
- }
937
- return child;
938
- });
939
- return summary;
940
- });
941
- const response = {
942
- epics: epicsWithStatus,
943
- total: result.total,
944
- limit: result.limit,
945
- offset: result.offset,
946
- };
947
- return { success: true, data: response };
948
- }
949
- async listAssignedEpicsTasks(params) {
950
- const validated = mcp_dto_1.ListAssignedEpicsTasksParamsSchema.parse(params);
951
- const ctx = await this.resolveSessionContext(validated.sessionId);
952
- if (!ctx.success)
953
- return ctx;
954
- const { project } = ctx.data;
955
- if (!project) {
956
- return {
957
- success: false,
958
- error: {
959
- code: 'PROJECT_NOT_FOUND',
960
- message: 'No project associated with this session',
961
- },
962
- };
963
- }
964
- const limit = validated.limit ?? 100;
965
- const offset = validated.offset ?? 0;
966
- try {
967
- const result = await this.storage.listAssignedEpics(project.id, {
968
- agentName: validated.agentName,
969
- limit,
970
- offset,
971
- excludeMcpHidden: true,
972
- });
973
- const statusesResult = await this.storage.listStatuses(project.id, {
974
- limit: 1000,
975
- offset: 0,
976
- });
977
- const statusById = new Map();
978
- for (const s of statusesResult.items)
979
- statusById.set(s.id, s);
980
- const agentIds = new Set();
981
- for (const epic of result.items) {
982
- if (epic.agentId)
983
- agentIds.add(epic.agentId);
984
- }
985
- const agentNameById = new Map();
986
- for (const agentId of agentIds) {
987
- try {
988
- const agent = await this.storage.getAgent(agentId);
989
- agentNameById.set(agentId, agent.name);
990
- }
991
- catch (error) {
992
- logger.warn({ agentId }, 'Failed to resolve agent name');
993
- }
994
- }
995
- const epicsWithStatus = result.items.map((epic) => {
996
- const summary = this.mapEpicSummary(epic, agentNameById);
997
- const s = statusById.get(epic.statusId);
998
- if (s) {
999
- summary.status = this.mapStatusSummary(s);
1000
- }
1001
- return summary;
1002
- });
1003
- const response = {
1004
- epics: epicsWithStatus,
1005
- total: result.total,
1006
- limit: result.limit,
1007
- offset: result.offset,
1008
- };
1009
- return { success: true, data: response };
1010
- }
1011
- catch (error) {
1012
- if (error instanceof error_types_1.NotFoundError) {
1013
- return {
1014
- success: false,
1015
- error: {
1016
- code: 'AGENT_NOT_FOUND',
1017
- message: `Agent "${validated.agentName}" was not found for project ${project.id}.`,
1018
- },
1019
- };
1020
- }
1021
- if (error instanceof error_types_1.ValidationError) {
1022
- return {
1023
- success: false,
1024
- error: {
1025
- code: 'VALIDATION_ERROR',
1026
- message: error.message,
1027
- data: error.details,
1028
- },
1029
- };
1030
- }
1031
- throw error;
1032
- }
1033
- }
1034
- async createEpic(params) {
1035
- if (!this.epicsService) {
1036
- return {
1037
- success: false,
1038
- error: {
1039
- code: 'SERVICE_UNAVAILABLE',
1040
- message: 'Epic creation requires full app context (not available in standalone MCP mode)',
1041
- },
1042
- };
1043
- }
1044
- const validated = mcp_dto_1.CreateEpicParamsSchema.parse(params);
1045
- const ctx = await this.resolveSessionContext(validated.sessionId);
1046
- if (!ctx.success)
1047
- return ctx;
1048
- const { project } = ctx.data;
1049
- if (!project) {
1050
- return {
1051
- success: false,
1052
- error: {
1053
- code: 'PROJECT_NOT_FOUND',
1054
- message: 'No project associated with this session',
1055
- },
1056
- };
1057
- }
1058
- let statusId;
1059
- if (validated.statusName) {
1060
- const status = await this.storage.findStatusByName(project.id, validated.statusName);
1061
- if (!status) {
1062
- const statusesResult = await this.storage.listStatuses(project.id, {
1063
- limit: 1000,
1064
- offset: 0,
1065
- });
1066
- return {
1067
- success: false,
1068
- error: {
1069
- code: 'STATUS_NOT_FOUND',
1070
- message: `Status "${validated.statusName}" was not found for project.`,
1071
- data: {
1072
- availableStatuses: statusesResult.items.map((s) => ({ id: s.id, name: s.label })),
1073
- },
1074
- },
1075
- };
1076
- }
1077
- statusId = status.id;
1078
- }
1079
- try {
1080
- const sessionCtx = ctx.data;
1081
- const actor = sessionCtx.type === 'agent'
1082
- ? { type: 'agent', id: sessionCtx.agent.id }
1083
- : sessionCtx.type === 'guest'
1084
- ? { type: 'guest', id: sessionCtx.guest.id }
1085
- : null;
1086
- const context = { actor };
1087
- const epic = await this.epicsService.createEpicForProject(project.id, {
1088
- title: validated.title,
1089
- description: validated.description ?? null,
1090
- statusId,
1091
- tags: validated.tags ?? [],
1092
- agentName: validated.agentName,
1093
- parentId: validated.parentId ?? null,
1094
- skillsRequired: validated.skillsRequired ?? null,
1095
- }, context);
1096
- let agentNameById;
1097
- if (epic.agentId) {
1098
- agentNameById = new Map();
1099
- try {
1100
- const agent = await this.storage.getAgent(epic.agentId);
1101
- agentNameById.set(epic.agentId, agent.name);
1102
- }
1103
- catch (error) {
1104
- logger.warn({ agentId: epic.agentId }, 'Failed to resolve agent name');
1105
- }
1106
- }
1107
- const response = {
1108
- epic: this.mapEpicSummary(epic, agentNameById),
1109
- };
1110
- return { success: true, data: response };
1111
- }
1112
- catch (error) {
1113
- if (error instanceof error_types_1.NotFoundError) {
1114
- return {
1115
- success: false,
1116
- error: {
1117
- code: 'AGENT_NOT_FOUND',
1118
- message: `Agent "${validated.agentName}" was not found for project ${project.id}.`,
1119
- },
1120
- };
1121
- }
1122
- if (error instanceof error_types_1.ValidationError) {
1123
- return {
1124
- success: false,
1125
- error: {
1126
- code: 'VALIDATION_ERROR',
1127
- message: error.message,
1128
- data: error.details,
1129
- },
1130
- };
1131
- }
1132
- throw error;
1133
- }
1134
- }
1135
- async getEpicById(params) {
1136
- const validated = mcp_dto_1.GetEpicByIdParamsSchema.parse(params);
1137
- const ctx = await this.resolveSessionContext(validated.sessionId);
1138
- if (!ctx.success)
1139
- return ctx;
1140
- const { project } = ctx.data;
1141
- if (!project) {
1142
- return {
1143
- success: false,
1144
- error: {
1145
- code: 'PROJECT_NOT_FOUND',
1146
- message: 'No project associated with this session',
1147
- },
1148
- };
1149
- }
1150
- let epic;
1151
- try {
1152
- epic = await this.storage.getEpic(validated.id);
1153
- }
1154
- catch (error) {
1155
- if (error instanceof error_types_1.NotFoundError) {
1156
- return {
1157
- success: false,
1158
- error: {
1159
- code: 'EPIC_NOT_FOUND',
1160
- message: `Epic ${validated.id} was not found.`,
1161
- },
1162
- };
1163
- }
1164
- throw error;
1165
- }
1166
- if (epic.projectId !== project.id) {
1167
- return {
1168
- success: false,
1169
- error: {
1170
- code: 'EPIC_NOT_FOUND',
1171
- message: `Epic ${validated.id} does not belong to the resolved project.`,
1172
- },
1173
- };
1174
- }
1175
- const commentsResult = await this.storage.listEpicComments(epic.id, {
1176
- limit: 250,
1177
- offset: 0,
1178
- });
1179
- const subEpicsResult = await this.storage.listSubEpics(epic.id, { limit: 250, offset: 0 });
1180
- let parentEpic;
1181
- if (epic.parentId) {
1182
- try {
1183
- const parent = await this.storage.getEpic(epic.parentId);
1184
- if (parent.projectId === project.id) {
1185
- parentEpic = parent;
1186
- }
1187
- }
1188
- catch (error) {
1189
- if (error instanceof error_types_1.NotFoundError) {
1190
- logger.warn({ epicId: epic.id, parentId: epic.parentId }, 'Parent epic missing');
1191
- }
1192
- else {
1193
- throw error;
1194
- }
1195
- }
1196
- }
1197
- const statusesResult = await this.storage.listStatuses(project.id, {
1198
- limit: 1000,
1199
- offset: 0,
1200
- });
1201
- const statusById = new Map();
1202
- for (const s of statusesResult.items)
1203
- statusById.set(s.id, s);
1204
- const agentIds = new Set();
1205
- if (epic.agentId)
1206
- agentIds.add(epic.agentId);
1207
- for (const child of subEpicsResult.items) {
1208
- if (child.agentId)
1209
- agentIds.add(child.agentId);
1210
- }
1211
- if (parentEpic?.agentId)
1212
- agentIds.add(parentEpic.agentId);
1213
- const agentNameById = new Map();
1214
- for (const agentId of agentIds) {
1215
- try {
1216
- const agent = await this.storage.getAgent(agentId);
1217
- agentNameById.set(agentId, agent.name);
1218
- }
1219
- catch (error) {
1220
- logger.warn({ agentId }, 'Failed to resolve agent name');
1221
- }
1222
- }
1223
- let parentSummary;
1224
- if (parentEpic) {
1225
- parentSummary = this.mapEpicParent(parentEpic, agentNameById);
1226
- }
1227
- const epicSummary = this.mapEpicSummary(epic, agentNameById);
1228
- const epicStatus = statusById.get(epic.statusId);
1229
- if (epicStatus) {
1230
- epicSummary.status = this.mapStatusSummary(epicStatus);
1231
- }
1232
- const subEpicsWithStatus = subEpicsResult.items.map((child) => {
1233
- const childSummary = this.mapEpicChild(child);
1234
- const childStatus = statusById.get(child.statusId);
1235
- if (childStatus) {
1236
- childSummary.status = this.mapStatusSummary(childStatus);
1237
- }
1238
- return childSummary;
1239
- });
1240
- const response = {
1241
- epic: epicSummary,
1242
- comments: [...commentsResult.items]
1243
- .reverse()
1244
- .map((comment, idx) => ({ ...this.mapEpicComment(comment), commentNumber: idx + 1 })),
1245
- subEpics: subEpicsWithStatus,
1246
- };
1247
- if (parentSummary) {
1248
- response.parent = parentSummary;
1249
- }
1250
- return { success: true, data: response };
1251
- }
1252
- async addEpicComment(params) {
1253
- const validated = mcp_dto_1.AddEpicCommentParamsSchema.parse(params);
1254
- const ctx = await this.resolveSessionContext(validated.sessionId);
1255
- if (!ctx.success)
1256
- return ctx;
1257
- const sessionCtx = ctx.data;
1258
- const authorActor = getActorFromContext(sessionCtx);
1259
- if (!authorActor) {
1260
- return {
1261
- success: false,
1262
- error: {
1263
- code: 'AGENT_REQUIRED',
1264
- message: 'Session must be associated with an agent or guest to add comments',
1265
- },
1266
- };
1267
- }
1268
- const project = sessionCtx.project;
1269
- if (!project) {
1270
- return {
1271
- success: false,
1272
- error: {
1273
- code: 'PROJECT_NOT_FOUND',
1274
- message: 'No project associated with this session',
1275
- },
1276
- };
1277
- }
1278
- let epic;
1279
- try {
1280
- epic = await this.storage.getEpic(validated.epicId);
1281
- }
1282
- catch (error) {
1283
- if (error instanceof error_types_1.NotFoundError) {
1284
- return {
1285
- success: false,
1286
- error: {
1287
- code: 'EPIC_NOT_FOUND',
1288
- message: `Epic ${validated.epicId} was not found.`,
1289
- },
1290
- };
1291
- }
1292
- throw error;
1293
- }
1294
- if (epic.projectId !== project.id) {
1295
- return {
1296
- success: false,
1297
- error: {
1298
- code: 'EPIC_NOT_FOUND',
1299
- message: `Epic ${validated.epicId} does not belong to the resolved project.`,
1300
- },
1301
- };
1302
- }
1303
- const comment = await this.storage.createEpicComment({
1304
- epicId: validated.epicId,
1305
- authorName: authorActor.name,
1306
- content: validated.content,
1307
- });
1308
- const response = {
1309
- comment: this.mapEpicComment(comment),
1310
- };
1311
- return { success: true, data: response };
1312
- }
1313
- async updateEpic(params) {
1314
- if (!this.epicsService) {
1315
- return {
1316
- success: false,
1317
- error: {
1318
- code: 'SERVICE_UNAVAILABLE',
1319
- message: 'Epic updates require full app context (not available in standalone MCP mode)',
1320
- },
1321
- };
1322
- }
1323
- let preprocessedParams = params;
1324
- if (params && typeof params === 'object' && 'assignment' in params) {
1325
- const p = params;
1326
- if (typeof p.assignment === 'string') {
1327
- try {
1328
- preprocessedParams = { ...p, assignment: JSON.parse(p.assignment) };
1329
- }
1330
- catch {
1331
- }
1332
- }
1333
- }
1334
- const validated = mcp_dto_1.UpdateEpicParamsSchema.parse(preprocessedParams);
1335
- const ctx = await this.resolveSessionContext(validated.sessionId);
1336
- if (!ctx.success)
1337
- return ctx;
1338
- const { project } = ctx.data;
1339
- if (!project) {
1340
- return {
1341
- success: false,
1342
- error: {
1343
- code: 'PROJECT_NOT_FOUND',
1344
- message: 'No project associated with this session',
1345
- },
1346
- };
1347
- }
1348
- let epic;
1349
- try {
1350
- epic = await this.storage.getEpic(validated.id);
1351
- }
1352
- catch (error) {
1353
- if (error instanceof error_types_1.NotFoundError) {
1354
- return {
1355
- success: false,
1356
- error: {
1357
- code: 'EPIC_NOT_FOUND',
1358
- message: `Epic ${validated.id} was not found.`,
1359
- },
1360
- };
1361
- }
1362
- throw error;
1363
- }
1364
- if (epic.projectId !== project.id) {
1365
- return {
1366
- success: false,
1367
- error: {
1368
- code: 'EPIC_NOT_FOUND',
1369
- message: `Epic ${validated.id} does not belong to the resolved project.`,
1370
- },
1371
- };
1372
- }
1373
- const updateData = {};
1374
- if (validated.title !== undefined) {
1375
- updateData.title = validated.title;
1376
- }
1377
- if (validated.description !== undefined) {
1378
- updateData.description = validated.description;
1379
- }
1380
- if (validated.skillsRequired !== undefined) {
1381
- updateData.skillsRequired = validated.skillsRequired;
1382
- }
1383
- if (validated.statusName) {
1384
- const status = await this.storage.findStatusByName(project.id, validated.statusName);
1385
- if (!status) {
1386
- const statusesResult = await this.storage.listStatuses(project.id, {
1387
- limit: 1000,
1388
- offset: 0,
1389
- });
1390
- return {
1391
- success: false,
1392
- error: {
1393
- code: 'STATUS_NOT_FOUND',
1394
- message: `Status "${validated.statusName}" was not found for project.`,
1395
- data: {
1396
- availableStatuses: statusesResult.items.map((s) => ({ id: s.id, name: s.label })),
1397
- },
1398
- },
1399
- };
1400
- }
1401
- updateData.statusId = status.id;
1402
- }
1403
- if (validated.assignment) {
1404
- if ('clear' in validated.assignment && validated.assignment.clear) {
1405
- updateData.agentId = null;
1406
- }
1407
- else if ('agentName' in validated.assignment) {
1408
- try {
1409
- const agent = await this.storage.getAgentByName(project.id, validated.assignment.agentName);
1410
- updateData.agentId = agent.id;
1411
- }
1412
- catch (error) {
1413
- if (error instanceof error_types_1.NotFoundError) {
1414
- const agentsList = await this.storage.listAgents(project.id, {
1415
- limit: 1000,
1416
- offset: 0,
1417
- });
1418
- return {
1419
- success: false,
1420
- error: {
1421
- code: 'AGENT_NOT_FOUND',
1422
- message: `Agent "${validated.assignment.agentName}" was not found for project.`,
1423
- data: {
1424
- availableAgents: agentsList.items.map((a) => ({ id: a.id, name: a.name })),
1425
- },
1426
- },
1427
- };
1428
- }
1429
- throw error;
1430
- }
1431
- }
1432
- }
1433
- if (validated.clearParent) {
1434
- updateData.parentId = null;
1435
- }
1436
- else if (validated.parentId !== undefined) {
1437
- if (validated.parentId === validated.id) {
1438
- return {
1439
- success: false,
1440
- error: {
1441
- code: 'PARENT_INVALID',
1442
- message: 'An epic cannot be its own parent.',
1443
- },
1444
- };
1445
- }
1446
- let parentEpic;
1447
- try {
1448
- parentEpic = await this.storage.getEpic(validated.parentId);
1449
- }
1450
- catch (error) {
1451
- if (error instanceof error_types_1.NotFoundError) {
1452
- return {
1453
- success: false,
1454
- error: {
1455
- code: 'PARENT_INVALID',
1456
- message: `Parent epic ${validated.parentId} was not found.`,
1457
- },
1458
- };
1459
- }
1460
- throw error;
1461
- }
1462
- if (parentEpic.projectId !== project.id) {
1463
- return {
1464
- success: false,
1465
- error: {
1466
- code: 'PARENT_INVALID',
1467
- message: 'Parent epic must belong to the same project.',
1468
- },
1469
- };
1470
- }
1471
- if (parentEpic.parentId !== null) {
1472
- return {
1473
- success: false,
1474
- error: {
1475
- code: 'HIERARCHY_CONFLICT',
1476
- message: 'Only one level of epic hierarchy is allowed. The specified parent already has a parent.',
1477
- },
1478
- };
1479
- }
1480
- updateData.parentId = validated.parentId;
1481
- }
1482
- if (validated.setTags !== undefined) {
1483
- updateData.tags = validated.setTags;
1484
- }
1485
- else if (validated.addTags || validated.removeTags) {
1486
- const currentTags = new Set(epic.tags);
1487
- if (validated.addTags) {
1488
- validated.addTags.forEach((tag) => currentTags.add(tag));
1489
- }
1490
- if (validated.removeTags) {
1491
- validated.removeTags.forEach((tag) => currentTags.delete(tag));
1492
- }
1493
- updateData.tags = Array.from(currentTags);
1494
- }
1495
- let updatedEpic;
1496
- try {
1497
- const sessionCtx = ctx.data;
1498
- const actor = sessionCtx.type === 'agent'
1499
- ? { type: 'agent', id: sessionCtx.agent.id }
1500
- : sessionCtx.type === 'guest'
1501
- ? { type: 'guest', id: sessionCtx.guest.id }
1502
- : null;
1503
- const context = { actor };
1504
- updatedEpic = await this.epicsService.updateEpic(validated.id, updateData, validated.version, context);
1505
- }
1506
- catch (error) {
1507
- if (error instanceof Error && error.message.includes('was modified by another operation')) {
1508
- const currentEpic = await this.storage.getEpic(validated.id);
1509
- return {
1510
- success: false,
1511
- error: {
1512
- code: 'VERSION_CONFLICT',
1513
- message: `Epic version conflict. Expected version ${validated.version}, but current version is ${currentEpic.version}.`,
1514
- data: {
1515
- currentVersion: currentEpic.version,
1516
- },
1517
- },
1518
- };
1519
- }
1520
- throw error;
1521
- }
1522
- let agentNameById;
1523
- if (updatedEpic.agentId) {
1524
- agentNameById = new Map();
1525
- try {
1526
- const agent = await this.storage.getAgent(updatedEpic.agentId);
1527
- agentNameById.set(updatedEpic.agentId, agent.name);
1528
- }
1529
- catch (error) {
1530
- logger.warn({ agentId: updatedEpic.agentId }, 'Failed to resolve agent name');
1531
- }
1532
- }
1533
- const response = {
1534
- epic: this.mapEpicSummary(updatedEpic, agentNameById),
1535
- };
1536
- return { success: true, data: response };
1537
- }
1538
- async resolveRecipientByName(projectId, name) {
1539
- try {
1540
- const agent = await this.storage.getAgentByName(projectId, name);
1541
- return {
1542
- type: 'agent',
1543
- id: agent.id,
1544
- name: agent.name,
1545
- };
1546
- }
1547
- catch (error) {
1548
- if (!(error instanceof error_types_1.NotFoundError)) {
1549
- throw error;
1550
- }
1551
- }
1552
- const guest = await this.storage.getGuestByName(projectId, name);
1553
- if (guest) {
1554
- return {
1555
- type: 'guest',
1556
- id: guest.id,
1557
- name: guest.name,
1558
- tmuxSessionId: guest.tmuxSessionId,
1559
- };
1560
- }
1561
- return null;
1562
- }
1563
- async getAvailableRecipientNames(projectId) {
1564
- const [agentsResult, guests] = await Promise.all([
1565
- this.storage.listAgents(projectId, { limit: 100, offset: 0 }),
1566
- this.storage.listGuests(projectId),
1567
- ]);
1568
- const agentNames = agentsResult.items.map((a) => a.name);
1569
- const guestNames = guests.map((g) => `${g.name} (guest)`);
1570
- return [...agentNames, ...guestNames];
1571
- }
1572
- async sendMessage(params) {
1573
- if (!this.sessionsService) {
1574
- return {
1575
- success: false,
1576
- error: {
1577
- code: 'SERVICE_UNAVAILABLE',
1578
- message: 'Chat functionality requires full app context (not available in standalone MCP mode)',
1579
- },
1580
- };
1581
- }
1582
- const validated = mcp_dto_1.SendMessageParamsSchema.parse(params);
1583
- const ctx = await this.resolveSessionContext(validated.sessionId);
1584
- if (!ctx.success)
1585
- return ctx;
1586
- const sessionCtx = ctx.data;
1587
- const sender = getActorFromContext(sessionCtx);
1588
- const project = sessionCtx.project;
1589
- if (!sender) {
1590
- return {
1591
- success: false,
1592
- error: {
1593
- code: 'AGENT_REQUIRED',
1594
- message: 'Session must be associated with an agent or guest to send messages',
1595
- },
1596
- };
1597
- }
1598
- if (!project) {
1599
- return {
1600
- success: false,
1601
- error: {
1602
- code: 'PROJECT_NOT_FOUND',
1603
- message: 'No project associated with this session',
1604
- },
1605
- };
1606
- }
1607
- if (sessionCtx.type === 'guest') {
1608
- if (validated.threadId) {
1609
- return {
1610
- success: false,
1611
- error: {
1612
- code: 'GUEST_THREAD_NOT_ALLOWED',
1613
- message: 'Guests cannot use threaded messaging. Use recipientAgentNames for direct messaging.',
1614
- },
1615
- };
1616
- }
1617
- if (validated.recipient === 'user') {
1618
- return {
1619
- success: false,
1620
- error: {
1621
- code: 'GUEST_USER_DM_NOT_ALLOWED',
1622
- message: 'Guests cannot send direct messages to users.',
1623
- },
1624
- };
1625
- }
1626
- }
1627
- try {
1628
- const autoLaunchSessions = process.env.NODE_ENV !== 'test';
1629
- const senderId = sender.id;
1630
- const senderName = sender.name;
1631
- const senderType = sessionCtx.type;
1632
- const recipientType = validated.recipient ?? 'agents';
1633
- const resolvedRecipients = [];
1634
- if (validated.recipientAgentNames && validated.recipientAgentNames.length > 0) {
1635
- for (const name of validated.recipientAgentNames) {
1636
- const recipient = await this.resolveRecipientByName(project.id, name);
1637
- if (!recipient) {
1638
- const availableNames = await this.getAvailableRecipientNames(project.id);
1639
- return {
1640
- success: false,
1641
- error: {
1642
- code: 'RECIPIENT_NOT_FOUND',
1643
- message: `Recipient "${name}" not found. Available: ${availableNames.join(', ') || 'none'}`,
1644
- },
1645
- };
1646
- }
1647
- if (recipient.id !== senderId) {
1648
- resolvedRecipients.push(recipient);
1649
- }
1650
- }
1651
- }
1652
- const uniqueRecipients = resolvedRecipients.filter((r, i, arr) => arr.findIndex((x) => x.id === r.id) === i);
1653
- if (!validated.threadId && senderId && recipientType !== 'user') {
1654
- if (!this.messagePoolService || !this.settingsService) {
1655
- return {
1656
- success: false,
1657
- error: {
1658
- code: 'SERVICE_UNAVAILABLE',
1659
- message: 'Message pool functionality requires full app context (not available in standalone MCP mode)',
1660
- },
1661
- };
1662
- }
1663
- if (uniqueRecipients.length === 0) {
1664
- return {
1665
- success: false,
1666
- error: {
1667
- code: 'RECIPIENTS_REQUIRED',
1668
- message: 'Recipients must be provided when sending without threadId.',
1669
- },
1670
- };
1671
- }
1672
- const queued = [];
1673
- const poolConfig = this.settingsService.getMessagePoolConfigForProject(project.id);
1674
- const activeSessions = this.sessionsService
1675
- ? await this.sessionsService.listActiveSessions()
1676
- : [];
1677
- for (const recipient of uniqueRecipients) {
1678
- const injectionText = `\n[This message is sent from "${senderName}" ${senderType} use devchain_send_message tool for communication]\n${validated.message}\n`;
1679
- if (recipient.type === 'agent') {
1680
- let session = activeSessions.find((s) => s.agentId === recipient.id);
1681
- let wasLaunched = false;
1682
- if (!session && autoLaunchSessions && this.sessionsService) {
1683
- try {
1684
- const launched = await this.sessionsService.launchSession({
1685
- projectId: project.id,
1686
- agentId: recipient.id,
1687
- options: { silent: true },
1688
- });
1689
- session = launched;
1690
- activeSessions.push(launched);
1691
- wasLaunched = true;
1692
- }
1693
- catch {
1694
- }
1695
- }
1696
- await this.messagePoolService.enqueue(recipient.id, injectionText, {
1697
- source: 'mcp.send_message',
1698
- submitKeys: ['Enter'],
1699
- senderAgentId: senderId,
1700
- projectId: project.id,
1701
- agentName: recipient.name,
1702
- });
1703
- queued.push({
1704
- name: recipient.name,
1705
- type: 'agent',
1706
- status: wasLaunched ? 'launched' : 'queued',
1707
- });
1708
- }
1709
- else {
1710
- const isOnline = this.tmuxService
1711
- ? await this.tmuxService.hasSession(recipient.tmuxSessionId)
1712
- : false;
1713
- if (!isOnline) {
1714
- queued.push({
1715
- name: recipient.name,
1716
- type: 'guest',
1717
- status: 'failed',
1718
- error: 'Recipient offline',
1719
- });
1720
- }
1721
- else if (this.tmuxService) {
1722
- try {
1723
- await this.tmuxService.pasteAndSubmit(recipient.tmuxSessionId, injectionText);
1724
- queued.push({
1725
- name: recipient.name,
1726
- type: 'guest',
1727
- status: 'delivered',
1728
- });
1729
- }
1730
- catch (error) {
1731
- logger.warn({ guestId: recipient.id, tmuxSessionId: recipient.tmuxSessionId, error }, 'Failed to deliver message to guest');
1732
- queued.push({
1733
- name: recipient.name,
1734
- type: 'guest',
1735
- status: 'failed',
1736
- error: error instanceof Error ? error.message : 'Delivery failed',
1737
- });
1738
- }
1739
- }
1740
- else {
1741
- queued.push({
1742
- name: recipient.name,
1743
- type: 'guest',
1744
- status: 'failed',
1745
- error: 'Tmux service unavailable',
1746
- });
1747
- }
1748
- }
1749
- }
1750
- const response = {
1751
- mode: 'pooled',
1752
- queuedCount: queued.length,
1753
- queued,
1754
- estimatedDeliveryMs: poolConfig.delayMs,
1755
- };
1756
- return { success: true, data: response };
1757
- }
1758
- if (!this.chatService) {
1759
- return {
1760
- success: false,
1761
- error: {
1762
- code: 'SERVICE_UNAVAILABLE',
1763
- message: 'Chat functionality requires full app context (not available in standalone MCP mode)',
1764
- },
1765
- };
1766
- }
1767
- let threadId = validated.threadId;
1768
- if (!threadId && senderId) {
1769
- if (recipientType === 'user') {
1770
- const direct = await this.chatService.createDirectThread({
1771
- projectId: project.id,
1772
- agentId: senderId,
1773
- });
1774
- threadId = direct.id;
1775
- }
1776
- else {
1777
- }
1778
- }
1779
- if (!threadId) {
1780
- return {
1781
- success: false,
1782
- error: {
1783
- code: 'THREAD_REQUIRED',
1784
- message: 'Unable to determine thread for message delivery',
1785
- },
1786
- };
1787
- }
1788
- const thread = await this.chatService.getThread(threadId);
1789
- const message = await this.chatService.createMessage(threadId, {
1790
- authorType: 'agent',
1791
- authorAgentId: senderId,
1792
- content: validated.message,
1793
- });
1794
- let targetAgentIds = uniqueRecipients.filter((r) => r.type === 'agent').map((r) => r.id);
1795
- if (senderId && thread.members && thread.members.length > 1 && targetAgentIds.length === 0) {
1796
- targetAgentIds = thread.members.filter((id) => id !== senderId);
1797
- }
1798
- const activeSessions = await this.sessionsService.listActiveSessions();
1799
- const delivered = [];
1800
- for (const agentId of targetAgentIds) {
1801
- const agent = await this.storage.getAgent(agentId);
1802
- let session = activeSessions.find((s) => s.agentId === agentId);
1803
- if (!session && autoLaunchSessions) {
1804
- try {
1805
- const launched = await this.sessionsService.launchSession({
1806
- projectId: project.id,
1807
- agentId,
1808
- options: { silent: true },
1809
- });
1810
- session = launched;
1811
- activeSessions.push(launched);
1812
- }
1813
- catch {
1814
- }
1815
- }
1816
- if (!session) {
1817
- delivered.push({ agentId, agentName: agent.name, sessionId: '', status: 'queued' });
1818
- continue;
1819
- }
1820
- 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`;
1821
- await this.sessionsService.injectTextIntoSession(session.id, injectionText);
1822
- delivered.push({
1823
- agentId,
1824
- agentName: agent.name,
1825
- sessionId: session.id,
1826
- status: 'delivered',
1827
- });
1828
- }
1829
- const response = {
1830
- mode: 'thread',
1831
- threadId,
1832
- messageId: message.id,
1833
- deliveryCount: delivered.filter((d) => d.status === 'delivered').length,
1834
- delivered,
1835
- };
1836
- return { success: true, data: response };
1837
- }
1838
- catch (error) {
1839
- logger.error({ error, params: redactParams(validated) }, 'sendMessage failed');
1840
- return {
1841
- success: false,
1842
- error: {
1843
- code: 'SEND_MESSAGE_FAILED',
1844
- message: error instanceof Error ? error.message : 'Failed to send message',
1845
- },
1846
- };
1847
- }
1848
- }
1849
- async chatAck(params) {
1850
- if (!this.chatService || !this.terminalGateway) {
1851
- return {
1852
- success: false,
1853
- error: {
1854
- code: 'SERVICE_UNAVAILABLE',
1855
- message: 'Chat acknowledgment requires full app context (not available in standalone MCP mode)',
1856
- },
1857
- };
1858
- }
1859
- const validated = mcp_dto_1.ChatAckParamsSchema.parse(params);
1860
- const { thread_id: threadId, message_id: messageId } = validated;
1861
- const ctx = await this.resolveSessionContext(validated.sessionId);
1862
- if (!ctx.success)
1863
- return ctx;
1864
- const sessionCtx = ctx.data;
1865
- const agent = getActorFromContext(sessionCtx);
1866
- if (!agent) {
1867
- return {
1868
- success: false,
1869
- error: {
1870
- code: 'AGENT_NOT_FOUND',
1871
- message: 'No agent associated with this session',
1872
- },
1873
- };
1874
- }
1875
- const agentId = agent.id;
1876
- try {
1877
- const thread = await this.chatService.getThread(threadId);
1878
- const memberIds = thread.members ?? [];
1879
- if (!memberIds.includes(agentId)) {
1880
- return {
1881
- success: false,
1882
- error: {
1883
- code: 'AGENT_NOT_IN_THREAD',
1884
- message: `Agent ${agent.name} is not a member of thread ${threadId}`,
1885
- },
1886
- };
1887
- }
1888
- const now = new Date().toISOString();
1889
- await this.storage.markMessageAsRead(messageId, agentId, now);
1890
- if (this.sessionsService) {
1891
- const activeSessions = await this.sessionsService.listActiveSessions();
1892
- const agentSession = activeSessions.find((s) => s.agentId === agentId);
1893
- if (agentSession && agentSession.tmuxSessionId) {
1894
- await this.chatService.acknowledgeInvite(threadId, messageId, agentId, agentSession.tmuxSessionId);
1895
- }
1896
- }
1897
- this.terminalGateway.broadcastEvent(`chat/${threadId}`, 'message.read', {
1898
- messageId,
1899
- agentId,
1900
- readAt: now,
1901
- });
1902
- const response = {
1903
- threadId,
1904
- messageId,
1905
- agentId,
1906
- agentName: agent.name,
1907
- acknowledged: true,
1908
- };
1909
- return { success: true, data: response };
1910
- }
1911
- catch (error) {
1912
- logger.error({ error, params: redactParams(validated) }, 'chatAck failed');
1913
- return {
1914
- success: false,
1915
- error: {
1916
- code: 'CHAT_ACK_FAILED',
1917
- message: error instanceof Error ? error.message : 'Failed to acknowledge message',
1918
- },
1919
- };
1920
- }
1921
- }
1922
- async chatListMembers(params) {
1923
- if (!this.chatService || !this.sessionsService) {
1924
- return {
1925
- success: false,
1926
- error: {
1927
- code: 'SERVICE_UNAVAILABLE',
1928
- message: 'Chat members listing requires full app context (not available in standalone MCP mode)',
1929
- },
1930
- };
1931
- }
1932
- const validated = mcp_dto_1.ChatListMembersParamsSchema.parse(params);
1933
- try {
1934
- const thread = await this.chatService.getThread(validated.thread_id);
1935
- const memberIds = thread.members ?? [];
1936
- if (memberIds.length === 0) {
1937
- const emptyResponse = {
1938
- thread: {
1939
- id: thread.id,
1940
- title: thread.title,
1941
- },
1942
- members: [],
1943
- total: 0,
1944
- };
1945
- return { success: true, data: emptyResponse };
1946
- }
1947
- const agents = await Promise.all(memberIds.map(async (agentId) => {
1948
- try {
1949
- return await this.storage.getAgent(agentId);
1950
- }
1951
- catch (error) {
1952
- logger.error({ error, agentId, threadId: thread.id }, 'Failed to resolve agent for chat members');
1953
- throw error;
1954
- }
1955
- }));
1956
- const activeSessions = await this.sessionsService.listActiveSessions();
1957
- const onlineAgents = new Set(activeSessions.map((session) => session.agentId));
1958
- const members = agents.map((agent) => ({
1959
- agent_id: agent.id,
1960
- agent_name: agent.name,
1961
- online: onlineAgents.has(agent.id),
1962
- }));
1963
- const response = {
1964
- thread: {
1965
- id: thread.id,
1966
- title: thread.title,
1967
- },
1968
- members,
1969
- total: members.length,
1970
- };
1971
- return { success: true, data: response };
1972
- }
1973
- catch (error) {
1974
- if (error instanceof common_1.NotFoundException || error instanceof error_types_1.NotFoundError) {
1975
- return {
1976
- success: false,
1977
- error: {
1978
- code: 'NOT_FOUND',
1979
- message: `Thread ${validated.thread_id} was not found.`,
1980
- },
1981
- };
1982
- }
1983
- logger.error({ error, params: validated }, 'chatListMembers failed');
1984
- return {
1985
- success: false,
1986
- error: {
1987
- code: 'CHAT_LIST_MEMBERS_FAILED',
1988
- message: error instanceof Error ? error.message : 'Failed to list chat members',
1989
- },
1990
- };
1991
- }
1992
- }
1993
- async chatReadHistory(params) {
1994
- if (!this.chatService) {
1995
- return {
1996
- success: false,
1997
- error: {
1998
- code: 'SERVICE_UNAVAILABLE',
1999
- message: 'Chat history requires full app context (not available in standalone MCP mode)',
2000
- },
2001
- };
2002
- }
2003
- const validated = mcp_dto_1.ChatReadHistoryParamsSchema.parse(params);
2004
- try {
2005
- const thread = await this.chatService.getThread(validated.thread_id);
2006
- const limit = validated.limit ?? 50;
2007
- const validatedWithExcludeSystem = validated;
2008
- const excludeSystem = typeof validatedWithExcludeSystem.exclude_system === 'boolean'
2009
- ? validatedWithExcludeSystem.exclude_system
2010
- : true;
2011
- const messagesList = await this.chatService.listMessages(validated.thread_id, {
2012
- since: validated.since,
2013
- limit,
2014
- offset: 0,
2015
- });
2016
- const authorIds = new Set();
2017
- const targetIds = new Set();
2018
- for (const m of messagesList.items) {
2019
- if (m.authorAgentId)
2020
- authorIds.add(m.authorAgentId);
2021
- if (m.targets)
2022
- for (const t of m.targets)
2023
- targetIds.add(t);
2024
- }
2025
- const idToName = new Map();
2026
- const toLoad = Array.from(new Set([...authorIds, ...targetIds]));
2027
- for (const id of toLoad) {
2028
- try {
2029
- const a = await this.storage.getAgent(id);
2030
- idToName.set(id, a.name);
2031
- }
2032
- catch {
2033
- }
2034
- }
2035
- const filteredItems = excludeSystem
2036
- ? messagesList.items.filter((m) => m.authorType !== 'system')
2037
- : messagesList.items;
2038
- const messages = filteredItems.map((m) => {
2039
- const base = {
2040
- id: m.id,
2041
- author_type: m.authorType,
2042
- author_agent_id: m.authorAgentId ?? null,
2043
- author_agent_name: m.authorAgentId ? (idToName.get(m.authorAgentId) ?? null) : null,
2044
- content: m.content,
2045
- created_at: m.createdAt,
2046
- targets: m.targets,
2047
- };
2048
- if (m.targets && m.targets.length > 0) {
2049
- const names = m.targets
2050
- .map((tid) => idToName.get(tid))
2051
- .filter((n) => typeof n === 'string' && n.length > 0);
2052
- if (names.length > 0) {
2053
- base.target_agent_names = names;
2054
- }
2055
- }
2056
- return base;
2057
- });
2058
- const response = {
2059
- thread: {
2060
- id: thread.id,
2061
- title: thread.title,
2062
- },
2063
- messages,
2064
- has_more: messages.length === limit,
2065
- };
2066
- return { success: true, data: response };
2067
- }
2068
- catch (error) {
2069
- if (error instanceof common_1.NotFoundException || error instanceof error_types_1.NotFoundError) {
2070
- return {
2071
- success: false,
2072
- error: {
2073
- code: 'NOT_FOUND',
2074
- message: `Thread ${validated.thread_id} was not found.`,
2075
- },
2076
- };
2077
- }
2078
- logger.error({ error, params: validated }, 'chatReadHistory failed');
2079
- return {
2080
- success: false,
2081
- error: {
2082
- code: 'CHAT_READ_HISTORY_FAILED',
2083
- message: error instanceof Error ? error.message : 'Failed to read chat history',
2084
- },
2085
- };
2086
- }
2087
- }
2088
- async activityStart(params) {
2089
- if (!this.chatService) {
2090
- return {
2091
- success: false,
2092
- error: { code: 'SERVICE_UNAVAILABLE', message: 'Chat service unavailable' },
2093
- };
2094
- }
2095
- const validated = mcp_dto_1.ActivityStartParamsSchema.parse(params);
2096
- const ctx = await this.resolveSessionContext(validated.sessionId);
2097
- if (!ctx.success)
2098
- return ctx;
2099
- const sessionCtx = ctx.data;
2100
- const project = sessionCtx.project;
2101
- const agent = getActorFromContext(sessionCtx);
2102
- if (sessionCtx.type === 'guest') {
2103
- return {
2104
- success: false,
2105
- error: {
2106
- code: 'GUEST_ACTIVITY_NOT_ALLOWED',
2107
- message: 'Guests cannot use activity tools.',
2108
- },
2109
- };
2110
- }
2111
- if (!project) {
2112
- return {
2113
- success: false,
2114
- error: {
2115
- code: 'PROJECT_NOT_FOUND',
2116
- message: 'No project associated with this session',
2117
- },
2118
- };
2119
- }
2120
- if (!agent) {
2121
- return {
2122
- success: false,
2123
- error: {
2124
- code: 'AGENT_NOT_FOUND',
2125
- message: 'No agent associated with this session',
2126
- },
2127
- };
2128
- }
2129
- const agentId = agent.id;
2130
- let threadId = validated.threadId;
2131
- if (threadId) {
2132
- const thread = await this.chatService.getThread(threadId);
2133
- const members = thread.members ?? [];
2134
- if (!members.includes(agentId)) {
2135
- return {
2136
- success: false,
2137
- error: {
2138
- code: 'AGENT_NOT_IN_THREAD',
2139
- message: `Agent ${agent.name} is not a member of thread ${threadId}`,
2140
- },
2141
- };
2142
- }
2143
- }
2144
- else {
2145
- const direct = await this.chatService.createDirectThread({ projectId: project.id, agentId });
2146
- threadId = direct.id;
2147
- }
2148
- const result = await this.chatService.startActivity(threadId, agentId, validated.title, {
2149
- announce: validated.announce,
2150
- });
2151
- const response = {
2152
- activity_id: result.activityId,
2153
- thread_id: threadId,
2154
- start_message_id: result.startMessageId,
2155
- started_at: result.startedAt,
2156
- auto_finished_prior: result.autoFinishedPrior,
2157
- };
2158
- return { success: true, data: response };
2159
- }
2160
- async activityFinish(params) {
2161
- if (!this.chatService) {
2162
- return {
2163
- success: false,
2164
- error: { code: 'SERVICE_UNAVAILABLE', message: 'Chat service unavailable' },
2165
- };
2166
- }
2167
- const validated = mcp_dto_1.ActivityFinishParamsSchema.parse(params);
2168
- const ctx = await this.resolveSessionContext(validated.sessionId);
2169
- if (!ctx.success)
2170
- return ctx;
2171
- const sessionCtx = ctx.data;
2172
- const project = sessionCtx.project;
2173
- const agent = getActorFromContext(sessionCtx);
2174
- if (sessionCtx.type === 'guest') {
2175
- return {
2176
- success: false,
2177
- error: {
2178
- code: 'GUEST_ACTIVITY_NOT_ALLOWED',
2179
- message: 'Guests cannot use activity tools.',
2180
- },
2181
- };
2182
- }
2183
- if (!project) {
2184
- return {
2185
- success: false,
2186
- error: {
2187
- code: 'PROJECT_NOT_FOUND',
2188
- message: 'No project associated with this session',
2189
- },
2190
- };
2191
- }
2192
- if (!agent) {
2193
- return {
2194
- success: false,
2195
- error: {
2196
- code: 'AGENT_NOT_FOUND',
2197
- message: 'No agent associated with this session',
2198
- },
2199
- };
2200
- }
2201
- const agentId = agent.id;
2202
- let threadId = validated.threadId;
2203
- if (threadId) {
2204
- const thread = await this.chatService.getThread(threadId);
2205
- const members = thread.members ?? [];
2206
- if (!members.includes(agentId)) {
2207
- return {
2208
- success: false,
2209
- error: {
2210
- code: 'AGENT_NOT_IN_THREAD',
2211
- message: `Agent ${agent.name} is not a member of thread ${threadId}`,
2212
- },
2213
- };
2214
- }
2215
- }
2216
- else {
2217
- const direct = await this.chatService.createDirectThread({ projectId: project.id, agentId });
2218
- threadId = direct.id;
2219
- }
2220
- try {
2221
- const result = await this.chatService.finishActivity(threadId, agentId, {
2222
- message: validated.message,
2223
- status: validated.status,
2224
- });
2225
- const response = {
2226
- activity_id: result.activityId,
2227
- thread_id: threadId,
2228
- finish_message_id: result.finishMessageId,
2229
- started_at: result.startedAt,
2230
- finished_at: result.finishedAt,
2231
- status: result.status,
2232
- };
2233
- return { success: true, data: response };
2234
- }
2235
- catch (error) {
2236
- if (error instanceof error_types_1.ValidationError || error instanceof common_2.BadRequestException) {
2237
- return {
2238
- success: false,
2239
- error: { code: 'NO_RUNNING_ACTIVITY', message: 'No running activity to finish' },
2240
- };
2241
- }
2242
- throw error;
2243
- }
2244
- }
2245
- async listSessions() {
2246
- if (!this.sessionsService) {
2247
- return {
2248
- success: false,
2249
- error: {
2250
- code: 'SERVICE_UNAVAILABLE',
2251
- message: 'Session listing requires full app context (not available in standalone MCP mode)',
2252
- },
2253
- };
2254
- }
2255
- try {
2256
- const activeSessions = await this.sessionsService.listActiveSessions();
2257
- const agentIds = [
2258
- ...new Set(activeSessions.map((s) => s.agentId).filter((id) => !!id)),
2259
- ];
2260
- const agentResults = await Promise.all(agentIds.map((id) => this.storage
2261
- .getAgent(id)
2262
- .catch(() => null)));
2263
- const agentMap = new Map(agentResults.filter((a) => a !== null).map((a) => [a.id, a]));
2264
- const projectIds = [
2265
- ...new Set(Array.from(agentMap.values())
2266
- .map((a) => a.projectId)
2267
- .filter((id) => !!id)),
2268
- ];
2269
- const projectResults = await Promise.all(projectIds.map((id) => this.storage.getProject(id).catch(() => null)));
2270
- const projectMap = new Map(projectResults.filter((p) => p !== null).map((p) => [p.id, p]));
2271
- const sessions = activeSessions.map((session) => {
2272
- const agent = session.agentId ? agentMap.get(session.agentId) : undefined;
2273
- const project = agent?.projectId ? projectMap.get(agent.projectId) : undefined;
2274
- return {
2275
- sessionIdShort: session.id.slice(0, 8),
2276
- agentName: agent?.name ?? 'Unknown',
2277
- projectName: agent ? (project?.name ?? 'Unknown') : '',
2278
- status: session.status,
2279
- startedAt: session.startedAt,
2280
- };
2281
- });
2282
- const response = { sessions };
2283
- return { success: true, data: response };
2284
- }
2285
- catch (error) {
2286
- logger.error({ error }, 'listSessions failed');
2287
- return {
2288
- success: false,
2289
- error: {
2290
- code: 'LIST_SESSIONS_FAILED',
2291
- message: error instanceof Error ? error.message : 'Failed to list sessions',
2292
- },
2293
- };
2294
- }
2295
- }
2296
- async resolveSessionContext(sessionId) {
2297
- if (!this.sessionsService) {
2298
- return {
2299
- success: false,
2300
- error: {
2301
- code: 'SERVICE_UNAVAILABLE',
2302
- message: 'Session resolution requires full app context (not available in standalone MCP mode)',
2303
- },
2304
- };
2305
- }
2306
- if (!sessionId || sessionId.length < 8) {
2307
- return {
2308
- success: false,
2309
- error: {
2310
- code: 'INVALID_SESSION_ID',
2311
- message: 'Session ID must be at least 8 characters (full UUID or prefix)',
2312
- },
2313
- };
2314
- }
2315
- try {
2316
- const activeSessions = await this.sessionsService.listActiveSessions();
2317
- let matchingSessions;
2318
- if (sessionId.length === 36) {
2319
- matchingSessions = activeSessions.filter((s) => s.id === sessionId);
2320
- }
2321
- else {
2322
- matchingSessions = activeSessions.filter((s) => s.id.startsWith(sessionId));
2323
- }
2324
- if (matchingSessions.length === 0) {
2325
- const guestContext = await this.tryResolveGuestContext(sessionId);
2326
- if (guestContext) {
2327
- return { success: true, data: guestContext };
2328
- }
2329
- return {
2330
- success: false,
2331
- error: {
2332
- code: 'SESSION_NOT_FOUND',
2333
- message: `No active session or guest found matching '${sessionId}'`,
2334
- },
2335
- };
2336
- }
2337
- if (matchingSessions.length > 1) {
2338
- const prefixLength = 12;
2339
- const matchingPrefixes = matchingSessions.map((s) => s.id.slice(0, prefixLength));
2340
- return {
2341
- success: false,
2342
- error: {
2343
- code: 'AMBIGUOUS_SESSION',
2344
- message: `Multiple active sessions match prefix '${sessionId}': ${matchingPrefixes.join(', ')}. Use a longer prefix (e.g., 12+ chars) or full UUID.`,
2345
- data: {
2346
- matchingSessionIdPrefixes: matchingPrefixes,
2347
- },
2348
- },
2349
- };
2350
- }
2351
- const session = matchingSessions[0];
2352
- let agent = null;
2353
- if (session.agentId) {
2354
- try {
2355
- const agentEntity = await this.storage.getAgent(session.agentId);
2356
- agent = {
2357
- id: agentEntity.id,
2358
- name: agentEntity.name,
2359
- projectId: agentEntity.projectId,
2360
- };
2361
- }
2362
- catch {
2363
- logger.warn({ sessionId: redactSessionId(session.id), agentId: session.agentId }, 'Agent not found for session');
2364
- }
2365
- }
2366
- let project = null;
2367
- if (agent?.projectId) {
2368
- try {
2369
- const projectEntity = await this.storage.getProject(agent.projectId);
2370
- project = {
2371
- id: projectEntity.id,
2372
- name: projectEntity.name,
2373
- rootPath: projectEntity.rootPath,
2374
- };
2375
- }
2376
- catch {
2377
- logger.warn({ sessionId: redactSessionId(session.id), projectId: agent.projectId }, 'Project not found for session');
2378
- }
2379
- }
2380
- const context = {
2381
- type: 'agent',
2382
- session: {
2383
- id: session.id,
2384
- agentId: session.agentId,
2385
- status: session.status,
2386
- startedAt: session.startedAt,
2387
- },
2388
- agent,
2389
- project,
2390
- };
2391
- return { success: true, data: context };
2392
- }
2393
- catch (error) {
2394
- logger.error({ error, sessionId: redactSessionId(sessionId) }, 'resolveSessionContext failed');
2395
- return {
2396
- success: false,
2397
- error: {
2398
- code: 'SESSION_RESOLUTION_FAILED',
2399
- message: error instanceof Error ? error.message : 'Failed to resolve session context',
2400
- },
2401
- };
2402
- }
2403
- }
2404
- async tryResolveGuestContext(guestId) {
2405
- if (!this.guestsService || !this.tmuxService) {
2406
- return null;
2407
- }
2408
- try {
2409
- let guest;
2410
- if (guestId.length === 36) {
2411
- guest = await this.storage.getGuest(guestId);
2412
- }
2413
- else {
2414
- const matches = await this.storage.getGuestsByIdPrefix(guestId);
2415
- if (matches.length === 1) {
2416
- guest = matches[0];
2417
- }
2418
- else {
2419
- return null;
2420
- }
2421
- }
2422
- const sessionAlive = await this.tmuxService.hasSession(guest.tmuxSessionId);
2423
- if (!sessionAlive) {
2424
- logger.warn({ guestId: guest.id, tmuxSessionId: guest.tmuxSessionId }, 'Guest tmux session no longer exists');
2425
- return null;
2426
- }
2427
- const project = await this.storage.getProject(guest.projectId);
2428
- const context = {
2429
- type: 'guest',
2430
- guest: {
2431
- id: guest.id,
2432
- name: guest.name,
2433
- projectId: guest.projectId,
2434
- tmuxSessionId: guest.tmuxSessionId,
2435
- },
2436
- project: {
2437
- id: project.id,
2438
- name: project.name,
2439
- rootPath: project.rootPath,
2440
- },
2441
- };
2442
- return context;
2443
- }
2444
- catch (error) {
2445
- logger.debug({ guestId: redactSessionId(guestId), error: String(error) }, 'Failed to resolve guest context');
2446
- return null;
2447
- }
2448
- }
2449
- async registerGuest(params) {
2450
- if (!this.guestsService) {
2451
- return {
2452
- success: false,
2453
- error: {
2454
- code: 'SERVICE_UNAVAILABLE',
2455
- message: 'Guest registration requires full app context',
2456
- },
2457
- };
2458
- }
2459
- const validated = mcp_dto_1.RegisterGuestParamsSchema.parse(params);
2460
- try {
2461
- const result = await this.guestsService.register({
2462
- name: validated.name,
2463
- tmuxSessionId: validated.tmuxSessionId,
2464
- description: validated.description,
2465
- });
2466
- const response = {
2467
- guestId: result.guestId,
2468
- name: validated.name,
2469
- projectId: result.projectId,
2470
- projectName: result.projectName,
2471
- isSandbox: result.isSandbox,
2472
- registeredAt: new Date().toISOString(),
2473
- };
2474
- logger.info({ guestId: result.guestId, projectId: result.projectId, isSandbox: result.isSandbox }, 'Guest registered successfully');
2475
- return { success: true, data: response };
2476
- }
2477
- catch (error) {
2478
- if (error instanceof error_types_1.ValidationError) {
2479
- return {
2480
- success: false,
2481
- error: {
2482
- code: 'VALIDATION_ERROR',
2483
- message: error.message,
2484
- data: error.details,
2485
- },
2486
- };
2487
- }
2488
- if (error instanceof Error && error.name === 'ConflictError') {
2489
- return {
2490
- success: false,
2491
- error: {
2492
- code: 'CONFLICT',
2493
- message: error.message,
2494
- data: error.data,
2495
- },
2496
- };
2497
- }
2498
- throw error;
2499
- }
2500
- }
2501
- mapStatusSummary(status) {
2502
- return {
2503
- id: status.id,
2504
- name: status.label,
2505
- position: status.position,
2506
- color: status.color,
2507
- };
2508
- }
2509
- mapEpicSummary(epic, agentNameById) {
2510
- const summary = {
2511
- id: epic.id,
2512
- title: epic.title,
2513
- description: epic.description ?? null,
2514
- statusId: epic.statusId,
2515
- version: epic.version,
2516
- };
2517
- if (epic.agentId && agentNameById) {
2518
- const agentName = agentNameById.get(epic.agentId);
2519
- if (agentName) {
2520
- summary.agentName = agentName;
2521
- }
2522
- }
2523
- if (epic.parentId) {
2524
- summary.parentId = epic.parentId;
2525
- }
2526
- summary.tags = epic.tags ?? [];
2527
- summary.skillsRequired = epic.skillsRequired ?? [];
2528
- return summary;
2529
- }
2530
- mapEpicChild(epic) {
2531
- return {
2532
- id: epic.id,
2533
- title: epic.title,
2534
- statusId: epic.statusId,
2535
- };
2536
- }
2537
- mapEpicParent(epic, agentNameById) {
2538
- return {
2539
- id: epic.id,
2540
- title: epic.title,
2541
- description: epic.description ?? null,
2542
- agentName: epic.agentId ? (agentNameById.get(epic.agentId) ?? null) : null,
2543
- };
2544
- }
2545
- mapEpicComment(comment) {
2546
- return {
2547
- id: comment.id,
2548
- authorName: comment.authorName,
2549
- content: comment.content,
2550
- createdAt: comment.createdAt,
2551
- };
2552
- }
2553
- mapDocumentSummary(document) {
2554
- return {
2555
- id: document.id,
2556
- projectId: document.projectId,
2557
- title: document.title,
2558
- slug: document.slug,
2559
- tags: document.tags,
2560
- archived: document.archived,
2561
- version: document.version,
2562
- updatedAt: document.updatedAt,
2563
- };
2564
- }
2565
- mapDocumentDetail(document) {
2566
- const summary = this.mapDocumentSummary(document);
2567
- return {
2568
- ...summary,
2569
- contentMd: document.contentMd,
2570
- createdAt: document.createdAt,
2571
- };
2572
- }
2573
- mapPromptSummary(prompt) {
2574
- return {
2575
- id: prompt.id,
2576
- projectId: prompt.projectId,
2577
- title: prompt.title,
2578
- contentPreview: prompt.contentPreview,
2579
- tags: prompt.tags,
2580
- version: prompt.version,
2581
- createdAt: prompt.createdAt,
2582
- updatedAt: prompt.updatedAt,
2583
- };
2584
- }
2585
- mapPromptDetail(prompt) {
2586
- const PREVIEW_LENGTH = 200;
2587
- const contentPreview = prompt.content.length > PREVIEW_LENGTH
2588
- ? prompt.content.slice(0, PREVIEW_LENGTH) + '…'
2589
- : prompt.content;
2590
- return {
2591
- id: prompt.id,
2592
- projectId: prompt.projectId,
2593
- title: prompt.title,
2594
- contentPreview,
2595
- content: prompt.content,
2596
- tags: prompt.tags,
2597
- version: prompt.version,
2598
- createdAt: prompt.createdAt,
2599
- updatedAt: prompt.updatedAt,
2600
- };
2601
- }
2602
- mapSkillListItem(skill) {
2603
- const description = skill.shortDescription ||
2604
- (skill.description ? skill.description.slice(0, 120) : 'No description available');
2605
- return {
2606
- slug: skill.slug,
2607
- description,
2608
- };
2609
- }
2610
- mapSkillDetail(skill) {
2611
- return {
2612
- slug: skill.slug,
2613
- name: skill.name,
2614
- description: skill.description,
2615
- instructionContent: skill.instructionContent,
2616
- contentPath: skill.contentPath,
2617
- resources: skill.resources,
2618
- sourceUrl: skill.sourceUrl,
2619
- license: skill.license,
2620
- compatibility: skill.compatibility,
2621
- status: skill.status,
2622
- frontmatter: skill.frontmatter,
2623
- };
2624
- }
2625
- extractLinkSlugs(content) {
2626
- const regex = /\[\[([A-Za-z0-9_\-./]+)\]\]/g;
2627
- const seen = new Set();
2628
- const slugs = [];
2629
- let match;
2630
- while ((match = regex.exec(content)) !== null) {
2631
- const slug = match[1].trim();
2632
- if (slug && !seen.has(slug)) {
2633
- seen.add(slug);
2634
- slugs.push(slug);
2635
- }
2636
- }
2637
- return slugs;
2638
- }
2639
- escapeForRegex(value) {
2640
- return value.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
2641
- }
2642
- async collectDocumentLinks(document) {
2643
- const cache = new Map();
2644
- cache.set(document.slug, document);
2645
- const slugs = this.extractLinkSlugs(document.contentMd);
2646
- const projectId = document.projectId ?? null;
2647
- const links = [];
2648
- for (const slug of slugs) {
2649
- const linked = await this.loadDocumentBySlug(projectId, slug, cache);
2650
- if (linked) {
2651
- links.push({
2652
- slug,
2653
- title: linked.title,
2654
- id: linked.id,
2655
- projectId: linked.projectId,
2656
- exists: true,
2657
- });
2658
- }
2659
- else {
2660
- links.push({ slug, exists: false });
2661
- }
2662
- }
2663
- return { links, cache };
2664
- }
2665
- async buildInlineResolution(document, cache, maxDepth, maxBytes) {
2666
- const effectiveDepth = Math.max(0, maxDepth);
2667
- const path = new Set([document.slug]);
2668
- const result = await this.inlineDocumentContent(document.contentMd, document.projectId ?? null, 0, { maxDepth: effectiveDepth, maxBytes }, cache, path);
2669
- const limited = this.applyByteLimit(result.content, maxBytes);
2670
- return {
2671
- contentMd: limited.content,
2672
- depthUsed: Math.min(result.depthUsed, effectiveDepth),
2673
- bytes: limited.bytes,
2674
- truncated: limited.truncated || result.truncated,
2675
- };
2676
- }
2677
- async inlineDocumentContent(content, projectId, depth, options, cache, path) {
2678
- if (options.maxDepth === 0 || depth >= options.maxDepth) {
2679
- const bytes = Buffer.byteLength(content, 'utf8');
2680
- return { content, depthUsed: depth, bytes, truncated: false };
2681
- }
2682
- let workingContent = content;
2683
- let depthUsed = depth;
2684
- const slugs = this.extractLinkSlugs(content);
2685
- for (const slug of slugs) {
2686
- if (depth >= options.maxDepth) {
2687
- break;
2688
- }
2689
- if (path.has(slug)) {
2690
- continue;
2691
- }
2692
- const linked = await this.loadDocumentBySlug(projectId, slug, cache);
2693
- if (!linked) {
2694
- continue;
2695
- }
2696
- path.add(slug);
2697
- const childResult = await this.inlineDocumentContent(linked.contentMd, linked.projectId ?? projectId, depth + 1, options, cache, path);
2698
- path.delete(slug);
2699
- depthUsed = Math.max(depthUsed, childResult.depthUsed);
2700
- const snippet = this.buildInlineSnippet(linked, childResult.content, depth + 1);
2701
- const pattern = new RegExp(`\\[\\[${this.escapeForRegex(slug)}\\]\\]`, 'g');
2702
- workingContent = workingContent.replace(pattern, snippet);
2703
- }
2704
- const bytes = Buffer.byteLength(workingContent, 'utf8');
2705
- return {
2706
- content: workingContent,
2707
- depthUsed: Math.max(depthUsed, depth),
2708
- bytes,
2709
- truncated: false,
2710
- };
2711
- }
2712
- buildInlineSnippet(document, content, depth) {
2713
- const headingLevel = Math.min(6, 2 + depth);
2714
- const heading = `${'#'.repeat(headingLevel)} ${document.title || document.slug}`;
2715
- return `\n\n---\n${heading}\n\n${content}\n---\n\n`;
2716
- }
2717
- async loadDocumentBySlug(projectId, slug, cache) {
2718
- if (cache.has(slug)) {
2719
- return cache.get(slug) ?? null;
2720
- }
2721
- try {
2722
- const linked = await this.storage.getDocument({ projectId, slug });
2723
- cache.set(slug, linked);
2724
- return linked;
2725
- }
2726
- catch (error) {
2727
- cache.set(slug, null);
2728
- return null;
2729
- }
2730
- }
2731
- applyByteLimit(content, maxBytes) {
2732
- const bytes = Buffer.byteLength(content, 'utf8');
2733
- if (bytes <= maxBytes) {
2734
- return { content, bytes, truncated: false };
2735
- }
2736
- const buffer = Buffer.from(content, 'utf8');
2737
- const truncatedBuffer = buffer.subarray(0, maxBytes);
2738
- return {
2739
- content: truncatedBuffer.toString('utf8'),
2740
- bytes: maxBytes,
2741
- truncated: true,
2742
- };
2743
- }
2744
- async resolveDocumentResource(uri) {
2745
- const spec = uri.slice('doc://'.length);
2746
- const slashIndex = spec.indexOf('/');
2747
- if (slashIndex === -1) {
2748
- throw new Error(`Invalid document resource URI: ${uri}`);
2749
- }
2750
- const projectPart = spec.slice(0, slashIndex);
2751
- const slugPart = spec.slice(slashIndex + 1);
2752
- const projectSlug = decodeURIComponent(projectPart);
2753
- const documentSlug = decodeURIComponent(slugPart);
2754
- const projectIdCandidate = await this.findProjectIdBySlug(projectSlug);
2755
- if (projectIdCandidate === undefined) {
2756
- return {
2757
- success: false,
2758
- error: {
2759
- code: 'PROJECT_NOT_FOUND',
2760
- message: `Unknown project slug: ${projectSlug}`,
2761
- },
2762
- };
2763
- }
2764
- try {
2765
- const document = await this.storage.getDocument({
2766
- projectId: projectIdCandidate ?? null,
2767
- slug: documentSlug,
2768
- });
2769
- return {
2770
- success: true,
2771
- data: {
2772
- uri,
2773
- mimeType: 'text/markdown',
2774
- content: document.contentMd,
2775
- document: this.mapDocumentDetail(document),
2776
- },
2777
- };
2778
- }
2779
- catch (error) {
2780
- return {
2781
- success: false,
2782
- error: {
2783
- code: 'DOCUMENT_NOT_FOUND',
2784
- message: `Document not found: ${documentSlug}`,
2785
- },
2786
- };
2787
- }
2788
- }
2789
- async resolvePromptResource(uri) {
2790
- const spec = uri.slice('prompt://'.length);
2791
- if (!spec) {
2792
- throw new Error(`Invalid prompt resource URI: ${uri}`);
2793
- }
2794
- const atIndex = spec.lastIndexOf('@');
2795
- const namePart = atIndex === -1 ? spec : spec.slice(0, atIndex);
2796
- const versionPart = atIndex === -1 ? undefined : spec.slice(atIndex + 1);
2797
- const name = decodeURIComponent(namePart).trim();
2798
- if (!name) {
2799
- throw new Error(`Prompt name missing in URI: ${uri}`);
2800
- }
2801
- let version;
2802
- if (versionPart && versionPart.length > 0) {
2803
- version = Number(versionPart);
2804
- if (!Number.isFinite(version) || version <= 0) {
2805
- throw new Error(`Invalid prompt version in URI: ${uri}`);
2806
- }
2807
- }
2808
- const list = await this.storage.listPrompts({ projectId: null });
2809
- const candidates = list.items.filter((prompt) => prompt.title === name && (version === undefined || prompt.version === version));
2810
- const selected = candidates.find((prompt) => prompt.projectId === null) ?? candidates[0];
2811
- if (!selected) {
2812
- return {
2813
- success: false,
2814
- error: {
2815
- code: 'PROMPT_NOT_FOUND',
2816
- message: `Prompt not found: ${name}${version ? `@${version}` : ''}`,
2817
- },
2818
- };
2819
- }
2820
- const prompt = await this.storage.getPrompt(selected.id);
2821
- return {
2822
- success: true,
2823
- data: {
2824
- uri,
2825
- mimeType: 'text/markdown',
2826
- content: prompt.content,
2827
- prompt: this.mapPromptDetail(prompt),
2828
- },
2829
- };
2830
- }
2831
- async findProjectIdBySlug(projectSlug) {
2832
- if (!projectSlug || projectSlug === 'global') {
2833
- return null;
2834
- }
2835
- const projects = await this.storage.listProjects({ limit: 1000, offset: 0 });
2836
- const match = projects.items.find((project) => this.slugifyProjectName(project.name) === projectSlug);
2837
- return match?.id;
2838
- }
2839
- slugifyProjectName(name) {
2840
- return name
2841
- .toLowerCase()
2842
- .replace(/[^a-z0-9]+/g, '-')
2843
- .replace(/(^-|-$)/g, '');
2844
- }
2845
- async listReviews(params) {
2846
- const validated = mcp_dto_1.ListReviewsParamsSchema.parse(params);
2847
- const ctx = await this.resolveSessionContext(validated.sessionId);
2848
- if (!ctx.success)
2849
- return ctx;
2850
- const { project } = ctx.data;
2851
- if (!project) {
2852
- return {
2853
- success: false,
2854
- error: {
2855
- code: 'PROJECT_NOT_FOUND',
2856
- message: 'No project associated with this session',
2857
- },
2858
- };
2859
- }
2860
- if (!this.reviewsService) {
2861
- return {
2862
- success: false,
2863
- error: {
2864
- code: 'SERVICE_UNAVAILABLE',
2865
- message: 'ReviewsService is not available',
2866
- },
2867
- };
2868
- }
2869
- const result = await this.reviewsService.listReviews(project.id, {
2870
- status: validated.status,
2871
- epicId: validated.epicId,
2872
- limit: validated.limit ?? 100,
2873
- offset: validated.offset ?? 0,
2874
- });
2875
- const reviews = result.items.map((review) => ({
2876
- id: review.id,
2877
- title: review.title,
2878
- description: review.description,
2879
- status: review.status,
2880
- baseRef: review.baseRef,
2881
- headRef: review.headRef,
2882
- baseSha: review.baseSha,
2883
- headSha: review.headSha,
2884
- epicId: review.epicId,
2885
- createdBy: review.createdBy,
2886
- createdByAgentId: review.createdByAgentId,
2887
- version: review.version,
2888
- commentCount: review.commentCount,
2889
- createdAt: review.createdAt,
2890
- updatedAt: review.updatedAt,
2891
- }));
2892
- const response = {
2893
- reviews,
2894
- total: result.total,
2895
- limit: result.limit,
2896
- offset: result.offset,
2897
- };
2898
- return { success: true, data: response };
2899
- }
2900
- async getReview(params) {
2901
- const validated = mcp_dto_1.GetReviewParamsSchema.parse(params);
2902
- const ctx = await this.resolveSessionContext(validated.sessionId);
2903
- if (!ctx.success)
2904
- return ctx;
2905
- const { project } = ctx.data;
2906
- if (!project) {
2907
- return {
2908
- success: false,
2909
- error: {
2910
- code: 'PROJECT_NOT_FOUND',
2911
- message: 'No project associated with this session',
2912
- },
2913
- };
2914
- }
2915
- if (!this.reviewsService) {
2916
- return {
2917
- success: false,
2918
- error: {
2919
- code: 'SERVICE_UNAVAILABLE',
2920
- message: 'ReviewsService is not available',
2921
- },
2922
- };
2923
- }
2924
- try {
2925
- const reviewWithFiles = await this.reviewsService.getReview(validated.reviewId);
2926
- if (reviewWithFiles.projectId !== project.id) {
2927
- return {
2928
- success: false,
2929
- error: {
2930
- code: 'REVIEW_NOT_FOUND',
2931
- message: `Review ${validated.reviewId} does not belong to this project`,
2932
- },
2933
- };
2934
- }
2935
- const commentsResult = await this.reviewsService.listComments(validated.reviewId, {
2936
- limit: 500,
2937
- });
2938
- const agentIds = new Set();
2939
- for (const comment of commentsResult.items) {
2940
- if (comment.authorAgentId)
2941
- agentIds.add(comment.authorAgentId);
2942
- }
2943
- const agentNameById = new Map();
2944
- for (const agentId of agentIds) {
2945
- try {
2946
- const agent = await this.storage.getAgent(agentId);
2947
- agentNameById.set(agentId, agent.name);
2948
- }
2949
- catch {
2950
- }
2951
- }
2952
- const comments = commentsResult.items.map((comment) => ({
2953
- id: comment.id,
2954
- filePath: comment.filePath,
2955
- lineStart: comment.lineStart,
2956
- lineEnd: comment.lineEnd,
2957
- side: comment.side,
2958
- content: comment.content,
2959
- commentType: comment.commentType,
2960
- status: comment.status,
2961
- authorType: comment.authorType,
2962
- authorAgentId: comment.authorAgentId,
2963
- authorAgentName: comment.authorAgentId
2964
- ? agentNameById.get(comment.authorAgentId)
2965
- : undefined,
2966
- parentId: comment.parentId,
2967
- version: comment.version,
2968
- createdAt: comment.createdAt,
2969
- updatedAt: comment.updatedAt,
2970
- }));
2971
- const changedFiles = (reviewWithFiles.changedFiles ?? []).map((file) => ({
2972
- path: file.path,
2973
- status: file.status,
2974
- additions: file.additions,
2975
- deletions: file.deletions,
2976
- oldPath: file.oldPath,
2977
- }));
2978
- const response = {
2979
- review: {
2980
- id: reviewWithFiles.id,
2981
- title: reviewWithFiles.title,
2982
- description: reviewWithFiles.description,
2983
- status: reviewWithFiles.status,
2984
- baseRef: reviewWithFiles.baseRef,
2985
- headRef: reviewWithFiles.headRef,
2986
- baseSha: reviewWithFiles.baseSha,
2987
- headSha: reviewWithFiles.headSha,
2988
- epicId: reviewWithFiles.epicId,
2989
- createdBy: reviewWithFiles.createdBy,
2990
- createdByAgentId: reviewWithFiles.createdByAgentId,
2991
- version: reviewWithFiles.version,
2992
- createdAt: reviewWithFiles.createdAt,
2993
- updatedAt: reviewWithFiles.updatedAt,
2994
- },
2995
- changedFiles,
2996
- comments,
2997
- };
2998
- return { success: true, data: response };
2999
- }
3000
- catch (error) {
3001
- if (error instanceof error_types_1.NotFoundError) {
3002
- return {
3003
- success: false,
3004
- error: {
3005
- code: 'REVIEW_NOT_FOUND',
3006
- message: `Review ${validated.reviewId} was not found`,
3007
- },
3008
- };
3009
- }
3010
- throw error;
3011
- }
3012
- }
3013
- async getReviewComments(params) {
3014
- const validated = mcp_dto_1.GetReviewCommentsParamsSchema.parse(params);
3015
- const ctx = await this.resolveSessionContext(validated.sessionId);
3016
- if (!ctx.success)
3017
- return ctx;
3018
- const { project } = ctx.data;
3019
- if (!project) {
3020
- return {
3021
- success: false,
3022
- error: {
3023
- code: 'PROJECT_NOT_FOUND',
3024
- message: 'No project associated with this session',
3025
- },
3026
- };
3027
- }
3028
- if (!this.reviewsService) {
3029
- return {
3030
- success: false,
3031
- error: {
3032
- code: 'SERVICE_UNAVAILABLE',
3033
- message: 'ReviewsService is not available',
3034
- },
3035
- };
3036
- }
3037
- try {
3038
- const review = await this.storage.getReview(validated.reviewId);
3039
- if (review.projectId !== project.id) {
3040
- return {
3041
- success: false,
3042
- error: {
3043
- code: 'REVIEW_NOT_FOUND',
3044
- message: `Review ${validated.reviewId} does not belong to this project`,
3045
- },
3046
- };
3047
- }
3048
- const result = await this.reviewsService.listComments(validated.reviewId, {
3049
- status: validated.status,
3050
- filePath: validated.filePath,
3051
- limit: validated.limit ?? 100,
3052
- offset: validated.offset ?? 0,
3053
- });
3054
- const agentIds = new Set();
3055
- for (const comment of result.items) {
3056
- if (comment.authorAgentId)
3057
- agentIds.add(comment.authorAgentId);
3058
- }
3059
- const agentNameById = new Map();
3060
- for (const agentId of agentIds) {
3061
- try {
3062
- const agent = await this.storage.getAgent(agentId);
3063
- agentNameById.set(agentId, agent.name);
3064
- }
3065
- catch {
3066
- }
3067
- }
3068
- const comments = result.items.map((comment) => ({
3069
- id: comment.id,
3070
- filePath: comment.filePath,
3071
- lineStart: comment.lineStart,
3072
- lineEnd: comment.lineEnd,
3073
- side: comment.side,
3074
- content: comment.content,
3075
- commentType: comment.commentType,
3076
- status: comment.status,
3077
- authorType: comment.authorType,
3078
- authorAgentId: comment.authorAgentId,
3079
- authorAgentName: comment.authorAgentId
3080
- ? agentNameById.get(comment.authorAgentId)
3081
- : undefined,
3082
- parentId: comment.parentId,
3083
- version: comment.version,
3084
- createdAt: comment.createdAt,
3085
- updatedAt: comment.updatedAt,
3086
- }));
3087
- const response = {
3088
- comments,
3089
- total: result.total,
3090
- limit: result.limit,
3091
- offset: result.offset,
3092
- };
3093
- return { success: true, data: response };
3094
- }
3095
- catch (error) {
3096
- if (error instanceof error_types_1.NotFoundError) {
3097
- return {
3098
- success: false,
3099
- error: {
3100
- code: 'REVIEW_NOT_FOUND',
3101
- message: `Review ${validated.reviewId} was not found`,
3102
- },
3103
- };
3104
- }
3105
- throw error;
3106
- }
3107
- }
3108
- async replyComment(params) {
3109
- const validated = mcp_dto_1.ReplyCommentParamsSchema.parse(params);
3110
- const ctx = await this.resolveSessionContext(validated.sessionId);
3111
- if (!ctx.success)
3112
- return ctx;
3113
- const { project } = ctx.data;
3114
- const actor = getActorFromContext(ctx.data);
3115
- if (!project) {
3116
- return {
3117
- success: false,
3118
- error: {
3119
- code: 'PROJECT_NOT_FOUND',
3120
- message: 'No project associated with this session',
3121
- },
3122
- };
3123
- }
3124
- if (!this.reviewsService) {
3125
- return {
3126
- success: false,
3127
- error: {
3128
- code: 'SERVICE_UNAVAILABLE',
3129
- message: 'ReviewsService is not available',
3130
- },
3131
- };
3132
- }
3133
- try {
3134
- const review = await this.storage.getReview(validated.reviewId);
3135
- if (review.projectId !== project.id) {
3136
- return {
3137
- success: false,
3138
- error: {
3139
- code: 'REVIEW_NOT_FOUND',
3140
- message: `Review ${validated.reviewId} does not belong to this project`,
3141
- },
3142
- };
3143
- }
3144
- const comment = await this.reviewsService.createComment(validated.reviewId, {
3145
- parentId: validated.parentCommentId,
3146
- content: validated.content,
3147
- filePath: validated.filePath,
3148
- lineStart: validated.lineStart,
3149
- lineEnd: validated.lineEnd,
3150
- commentType: validated.commentType ?? 'comment',
3151
- authorType: 'agent',
3152
- authorAgentId: actor?.id,
3153
- targetAgentIds: validated.targetAgentIds,
3154
- });
3155
- const response = {
3156
- comment: {
3157
- id: comment.id,
3158
- filePath: comment.filePath,
3159
- lineStart: comment.lineStart,
3160
- lineEnd: comment.lineEnd,
3161
- side: comment.side,
3162
- content: comment.content,
3163
- commentType: comment.commentType,
3164
- status: comment.status,
3165
- authorType: comment.authorType,
3166
- authorAgentId: comment.authorAgentId,
3167
- authorAgentName: actor?.name,
3168
- parentId: comment.parentId,
3169
- version: comment.version,
3170
- createdAt: comment.createdAt,
3171
- updatedAt: comment.updatedAt,
3172
- },
3173
- };
3174
- return { success: true, data: response };
3175
- }
3176
- catch (error) {
3177
- if (error instanceof error_types_1.NotFoundError) {
3178
- return {
3179
- success: false,
3180
- error: {
3181
- code: 'REVIEW_NOT_FOUND',
3182
- message: `Review ${validated.reviewId} was not found`,
3183
- },
3184
- };
3185
- }
3186
- throw error;
3187
- }
3188
- }
3189
- async resolveComment(params) {
3190
- const validated = mcp_dto_1.ResolveCommentParamsSchema.parse(params);
3191
- const ctx = await this.resolveSessionContext(validated.sessionId);
3192
- if (!ctx.success)
3193
- return ctx;
3194
- const { project } = ctx.data;
3195
- if (!project) {
3196
- return {
3197
- success: false,
3198
- error: {
3199
- code: 'PROJECT_NOT_FOUND',
3200
- message: 'No project associated with this session',
3201
- },
3202
- };
3203
- }
3204
- if (!this.reviewsService) {
3205
- return {
3206
- success: false,
3207
- error: {
3208
- code: 'SERVICE_UNAVAILABLE',
3209
- message: 'ReviewsService is not available',
3210
- },
3211
- };
3212
- }
3213
- try {
3214
- const comment = await this.storage.getReviewComment(validated.commentId);
3215
- const review = await this.storage.getReview(comment.reviewId);
3216
- if (review.projectId !== project.id) {
3217
- return {
3218
- success: false,
3219
- error: {
3220
- code: 'COMMENT_NOT_FOUND',
3221
- message: `Comment ${validated.commentId} does not belong to this project`,
3222
- },
3223
- };
3224
- }
3225
- const updatedComment = await this.reviewsService.resolveComment(comment.reviewId, validated.commentId, validated.resolution, validated.version);
3226
- let authorAgentName;
3227
- if (updatedComment.authorAgentId) {
3228
- try {
3229
- const agent = await this.storage.getAgent(updatedComment.authorAgentId);
3230
- authorAgentName = agent.name;
3231
- }
3232
- catch {
3233
- }
3234
- }
3235
- const response = {
3236
- comment: {
3237
- id: updatedComment.id,
3238
- filePath: updatedComment.filePath,
3239
- lineStart: updatedComment.lineStart,
3240
- lineEnd: updatedComment.lineEnd,
3241
- side: updatedComment.side,
3242
- content: updatedComment.content,
3243
- commentType: updatedComment.commentType,
3244
- status: updatedComment.status,
3245
- authorType: updatedComment.authorType,
3246
- authorAgentId: updatedComment.authorAgentId,
3247
- authorAgentName,
3248
- parentId: updatedComment.parentId,
3249
- version: updatedComment.version,
3250
- createdAt: updatedComment.createdAt,
3251
- updatedAt: updatedComment.updatedAt,
3252
- },
3253
- };
3254
- return { success: true, data: response };
3255
- }
3256
- catch (error) {
3257
- if (error instanceof error_types_1.NotFoundError) {
3258
- return {
3259
- success: false,
3260
- error: {
3261
- code: 'COMMENT_NOT_FOUND',
3262
- message: `Comment ${validated.commentId} was not found`,
3263
- },
3264
- };
3265
- }
3266
- throw error;
3267
- }
3268
- }
3269
- async applySuggestion(params) {
3270
- const validated = mcp_dto_1.ApplySuggestionParamsSchema.parse(params);
3271
- const ctx = await this.resolveSessionContext(validated.sessionId);
3272
- if (!ctx.success)
3273
- return ctx;
3274
- const { project } = ctx.data;
3275
- if (!project) {
3276
- return {
3277
- success: false,
3278
- error: {
3279
- code: 'PROJECT_NOT_FOUND',
3280
- message: 'No project associated with this session',
3281
- },
3282
- };
3283
- }
3284
- if (!this.reviewsService) {
3285
- return {
3286
- success: false,
3287
- error: {
3288
- code: 'SERVICE_UNAVAILABLE',
3289
- message: 'ReviewsService is not available',
3290
- },
3291
- };
3292
- }
3293
- try {
3294
- const comment = await this.storage.getReviewComment(validated.commentId);
3295
- const review = await this.storage.getReview(comment.reviewId);
3296
- if (review.projectId !== project.id) {
3297
- return {
3298
- success: false,
3299
- error: {
3300
- code: 'COMMENT_NOT_FOUND',
3301
- message: `Comment ${validated.commentId} does not belong to this project`,
3302
- },
3303
- };
3304
- }
3305
- if (!comment.filePath || comment.lineStart === null) {
3306
- return {
3307
- success: false,
3308
- error: {
3309
- code: 'INVALID_SUGGESTION',
3310
- message: 'Comment does not have file path or line information',
3311
- },
3312
- };
3313
- }
3314
- const suggestionMatch = comment.content.match(/```suggestion\s*\n([\s\S]*?)```/);
3315
- if (!suggestionMatch) {
3316
- return {
3317
- success: false,
3318
- error: {
3319
- code: 'NO_SUGGESTION',
3320
- message: 'Comment does not contain a suggestion block',
3321
- },
3322
- };
3323
- }
3324
- const suggestedCode = suggestionMatch[1].trimEnd();
3325
- const lineStart = comment.lineStart;
3326
- const lineEnd = comment.lineEnd ?? comment.lineStart;
3327
- let validatedPath;
3328
- try {
3329
- validatedPath = (0, path_validation_1.validatePathWithinRoot)(project.rootPath, comment.filePath, {
3330
- errorPrefix: 'Invalid file path in comment',
3331
- });
3332
- }
3333
- catch (error) {
3334
- if (error instanceof error_types_1.ValidationError) {
3335
- return {
3336
- success: false,
3337
- error: {
3338
- code: 'PATH_TRAVERSAL_BLOCKED',
3339
- message: error.message,
3340
- data: error.details,
3341
- },
3342
- };
3343
- }
3344
- throw error;
3345
- }
3346
- let realFilePath;
3347
- try {
3348
- realFilePath = await (0, path_validation_1.validateResolvedPathWithinRoot)(validatedPath.absolutePath, project.rootPath, { errorPrefix: 'Symlink validation failed' });
3349
- }
3350
- catch (error) {
3351
- if (error instanceof error_types_1.ValidationError) {
3352
- return {
3353
- success: false,
3354
- error: {
3355
- code: 'SYMLINK_ESCAPE_BLOCKED',
3356
- message: error.message,
3357
- data: error.details,
3358
- },
3359
- };
3360
- }
3361
- throw error;
3362
- }
3363
- const fs = await Promise.resolve().then(() => __importStar(require('fs/promises')));
3364
- const filePath = realFilePath;
3365
- const fileContent = await fs.readFile(filePath, 'utf-8');
3366
- const lines = fileContent.split('\n');
3367
- try {
3368
- (0, path_validation_1.validateLineBounds)(lineStart, lineEnd, lines.length);
3369
- }
3370
- catch (error) {
3371
- if (error instanceof error_types_1.ValidationError) {
3372
- return {
3373
- success: false,
3374
- error: {
3375
- code: 'INVALID_LINE_BOUNDS',
3376
- message: error.message,
3377
- data: error.details,
3378
- },
3379
- };
3380
- }
3381
- throw error;
3382
- }
3383
- const suggestedLines = suggestedCode.split('\n');
3384
- lines.splice(lineStart - 1, lineEnd - lineStart + 1, ...suggestedLines);
3385
- await fs.writeFile(filePath, lines.join('\n'), 'utf-8');
3386
- const updatedComment = await this.reviewsService.resolveComment(comment.reviewId, validated.commentId, 'resolved', validated.version);
3387
- let authorAgentName;
3388
- if (updatedComment.authorAgentId) {
3389
- try {
3390
- const agent = await this.storage.getAgent(updatedComment.authorAgentId);
3391
- authorAgentName = agent.name;
3392
- }
3393
- catch {
3394
- }
3395
- }
3396
- const response = {
3397
- comment: {
3398
- id: updatedComment.id,
3399
- filePath: updatedComment.filePath,
3400
- lineStart: updatedComment.lineStart,
3401
- lineEnd: updatedComment.lineEnd,
3402
- side: updatedComment.side,
3403
- content: updatedComment.content,
3404
- commentType: updatedComment.commentType,
3405
- status: updatedComment.status,
3406
- authorType: updatedComment.authorType,
3407
- authorAgentId: updatedComment.authorAgentId,
3408
- authorAgentName,
3409
- parentId: updatedComment.parentId,
3410
- version: updatedComment.version,
3411
- createdAt: updatedComment.createdAt,
3412
- updatedAt: updatedComment.updatedAt,
3413
- },
3414
- applied: {
3415
- filePath: comment.filePath,
3416
- lineStart,
3417
- lineEnd,
3418
- suggestedCode,
3419
- },
3420
- };
3421
- return { success: true, data: response };
3422
- }
3423
- catch (error) {
3424
- if (error instanceof error_types_1.NotFoundError) {
3425
- return {
3426
- success: false,
3427
- error: {
3428
- code: 'COMMENT_NOT_FOUND',
3429
- message: `Comment ${validated.commentId} was not found`,
3430
- },
3431
- };
3432
- }
3433
- if (error.code === 'ENOENT') {
3434
- return {
3435
- success: false,
3436
- error: {
3437
- code: 'FILE_NOT_FOUND',
3438
- message: `File not found at path`,
3439
- },
3440
- };
3441
- }
3442
- throw error;
3443
- }
210
+ async resolveSessionContext(sessionId) {
211
+ return this.sessionContextResolver.resolve(sessionId);
3444
212
  }
3445
213
  };
3446
214
  exports.McpService = McpService;