@vfarcic/dot-ai 1.10.2 → 1.12.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 (56) hide show
  1. package/dist/core/ai-provider-factory.d.ts.map +1 -1
  2. package/dist/core/ai-provider-factory.js +1 -0
  3. package/dist/core/error-handling.js +1 -1
  4. package/dist/core/git-utils.d.ts +5 -0
  5. package/dist/core/git-utils.d.ts.map +1 -1
  6. package/dist/core/git-utils.js +16 -0
  7. package/dist/core/internal-tools.d.ts +35 -0
  8. package/dist/core/internal-tools.d.ts.map +1 -0
  9. package/dist/core/internal-tools.js +286 -0
  10. package/dist/core/mcp-client-manager.d.ts +88 -0
  11. package/dist/core/mcp-client-manager.d.ts.map +1 -0
  12. package/dist/core/mcp-client-manager.js +362 -0
  13. package/dist/core/mcp-client-registry.d.ts +36 -0
  14. package/dist/core/mcp-client-registry.d.ts.map +1 -0
  15. package/dist/core/mcp-client-registry.js +52 -0
  16. package/dist/core/mcp-client-types.d.ts +84 -0
  17. package/dist/core/mcp-client-types.d.ts.map +1 -0
  18. package/dist/core/mcp-client-types.js +24 -0
  19. package/dist/core/model-config.d.ts +1 -0
  20. package/dist/core/model-config.d.ts.map +1 -1
  21. package/dist/core/model-config.js +1 -0
  22. package/dist/core/providers/vercel-provider.d.ts.map +1 -1
  23. package/dist/core/providers/vercel-provider.js +5 -0
  24. package/dist/interfaces/mcp.d.ts.map +1 -1
  25. package/dist/interfaces/mcp.js +13 -0
  26. package/dist/mcp/server.js +26 -1
  27. package/dist/tools/generate-manifests.d.ts.map +1 -1
  28. package/dist/tools/generate-manifests.js +2 -1
  29. package/dist/tools/impact-analysis.d.ts +51 -0
  30. package/dist/tools/impact-analysis.d.ts.map +1 -0
  31. package/dist/tools/impact-analysis.js +204 -0
  32. package/dist/tools/index.d.ts +1 -0
  33. package/dist/tools/index.d.ts.map +1 -1
  34. package/dist/tools/index.js +6 -1
  35. package/dist/tools/operate-analysis.d.ts +1 -1
  36. package/dist/tools/operate-analysis.d.ts.map +1 -1
  37. package/dist/tools/operate-analysis.js +17 -6
  38. package/dist/tools/operate.d.ts +1 -1
  39. package/dist/tools/operate.d.ts.map +1 -1
  40. package/dist/tools/operate.js +1 -1
  41. package/dist/tools/organizational-data.d.ts +1 -1
  42. package/dist/tools/push-to-git.d.ts.map +1 -1
  43. package/dist/tools/push-to-git.js +1 -11
  44. package/dist/tools/query.d.ts +1 -1
  45. package/dist/tools/query.d.ts.map +1 -1
  46. package/dist/tools/query.js +18 -8
  47. package/dist/tools/remediate.d.ts +9 -0
  48. package/dist/tools/remediate.d.ts.map +1 -1
  49. package/dist/tools/remediate.js +45 -10
  50. package/dist/tools/version.d.ts +12 -0
  51. package/dist/tools/version.d.ts.map +1 -1
  52. package/dist/tools/version.js +16 -1
  53. package/package.json +3 -1
  54. package/prompts/impact-analysis-system.md +32 -0
  55. package/prompts/operate-system.md +3 -3
  56. package/prompts/remediate-system.md +62 -2
@@ -1 +1 @@
1
- {"version":3,"file":"ai-provider-factory.d.ts","sourceRoot":"","sources":["../../src/core/ai-provider-factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,UAAU,EACV,gBAAgB,EACjB,MAAM,yBAAyB,CAAC;AA6BjC;;;;;;;;;;;;;;GAcG;AACH,qBAAa,iBAAiB;IAC5B;;;;;;OAMG;IACH,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU;IA2BnD;;;;;;;;;;OAUG;IACH,MAAM,CAAC,aAAa,IAAI,UAAU;IAoFlC;;;;;OAKG;IACH,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAWrD;;;;OAIG;IACH,MAAM,CAAC,qBAAqB,IAAI,MAAM,EAAE;IAMxC;;;;;OAKG;IACH,MAAM,CAAC,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;CAGxD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,UAAU,CAE7C"}
1
+ {"version":3,"file":"ai-provider-factory.d.ts","sourceRoot":"","sources":["../../src/core/ai-provider-factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,UAAU,EACV,gBAAgB,EACjB,MAAM,yBAAyB,CAAC;AA8BjC;;;;;;;;;;;;;;GAcG;AACH,qBAAa,iBAAiB;IAC5B;;;;;;OAMG;IACH,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU;IA2BnD;;;;;;;;;;OAUG;IACH,MAAM,CAAC,aAAa,IAAI,UAAU;IAoFlC;;;;;OAKG;IACH,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAWrD;;;;OAIG;IACH,MAAM,CAAC,qBAAqB,IAAI,MAAM,EAAE;IAMxC;;;;;OAKG;IACH,MAAM,CAAC,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;CAGxD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,UAAU,CAE7C"}
@@ -28,6 +28,7 @@ const PROVIDER_ENV_KEYS = {
28
28
  google: 'GOOGLE_GENERATIVE_AI_API_KEY', // Standard Vercel AI SDK env var (also checks GOOGLE_API_KEY as fallback)
29
29
  google_flash: 'GOOGLE_GENERATIVE_AI_API_KEY', // PRD #294: Uses same API key as regular Google
30
30
  kimi: 'MOONSHOT_API_KEY', // PRD #353: Moonshot AI Kimi K2.5
31
+ alibaba: 'ALIBABA_API_KEY', // PRD #382: Alibaba Qwen 3.5 Plus
31
32
  xai: 'XAI_API_KEY',
32
33
  };
33
34
  const IMPLEMENTED_PROVIDERS = Object.keys(model_config_1.CURRENT_MODELS);
@@ -109,7 +109,7 @@ class ConsoleLogger {
109
109
  const timestamp = new Date().toISOString();
110
110
  const baseMessage = `[${timestamp}] ${level.toUpperCase()} [${this.component}] ${message}`;
111
111
  if (data) {
112
- return `${baseMessage} ${JSON.stringify(data, null, 2)}`;
112
+ return `${baseMessage} ${JSON.stringify(data)}`;
113
113
  }
114
114
  return baseMessage;
115
115
  }
@@ -23,6 +23,11 @@ export declare function scrubCredentials(message: string): string;
23
23
  export declare function getAuthenticatedUrl(repoUrl: string, token: string): string;
24
24
  export declare function getAuthToken(authConfig: GitAuthConfig): Promise<string>;
25
25
  export declare function getGitAuthConfigFromEnv(): GitAuthConfig;
26
+ /**
27
+ * Sanitize a relative path to prevent directory traversal.
28
+ * Rejects absolute paths and paths that escape the base directory.
29
+ */
30
+ export declare function sanitizeRelativePath(relativePath: string): string;
26
31
  export interface CloneOptions {
27
32
  branch?: string;
28
33
  depth?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"git-utils.d.ts","sourceRoot":"","sources":["../../src/core/git-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAYH,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE;QACV,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;CACH;AASD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIxD;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAK1E;AA2ED,wBAAsB,YAAY,CAAC,UAAU,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAc7E;AAED,wBAAgB,uBAAuB,IAAI,aAAa,CAyBvD;AAeD,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,YAAY,GAClB,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CA6BhD;AAID,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CA8B5E;AAID,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1C;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,wBAAsB,QAAQ,CAC5B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,EAC/C,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,UAAU,CAAC,CAyErB"}
1
+ {"version":3,"file":"git-utils.d.ts","sourceRoot":"","sources":["../../src/core/git-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAYH,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE;QACV,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;CACH;AASD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIxD;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAK1E;AA2ED,wBAAsB,YAAY,CAAC,UAAU,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAc7E;AAED,wBAAgB,uBAAuB,IAAI,aAAa,CAyBvD;AAeD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CASjE;AAID,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,SAAS,CAC7B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,YAAY,GAClB,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CA6BhD;AAID,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CA8B5E;AAID,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1C;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,wBAAsB,QAAQ,CAC5B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,EAC/C,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,UAAU,CAAC,CAyErB"}
@@ -53,6 +53,7 @@ exports.scrubCredentials = scrubCredentials;
53
53
  exports.getAuthenticatedUrl = getAuthenticatedUrl;
54
54
  exports.getAuthToken = getAuthToken;
55
55
  exports.getGitAuthConfigFromEnv = getGitAuthConfigFromEnv;
56
+ exports.sanitizeRelativePath = sanitizeRelativePath;
56
57
  exports.cloneRepo = cloneRepo;
57
58
  exports.pullRepo = pullRepo;
58
59
  exports.pushRepo = pushRepo;
@@ -161,6 +162,21 @@ function gitOptions(baseDir) {
161
162
  timeout: { block: GIT_TIMEOUT_MS },
162
163
  };
163
164
  }
165
+ // ─── Path safety ───
166
+ /**
167
+ * Sanitize a relative path to prevent directory traversal.
168
+ * Rejects absolute paths and paths that escape the base directory.
169
+ */
170
+ function sanitizeRelativePath(relativePath) {
171
+ if (relativePath.startsWith('/')) {
172
+ throw new Error('Relative path cannot be absolute');
173
+ }
174
+ const normalized = path.posix.normalize(relativePath);
175
+ if (normalized.startsWith('..') || path.posix.isAbsolute(normalized)) {
176
+ throw new Error('Relative path cannot escape target directory');
177
+ }
178
+ return normalized;
179
+ }
164
180
  async function cloneRepo(repoUrl, targetDir, opts) {
165
181
  const authConfig = getGitAuthConfigFromEnv();
166
182
  let cloneUrl;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Internal Agentic-Loop Tools (PRD #407)
3
+ *
4
+ * Tools that run locally in the MCP server, available to the AI
5
+ * during investigation loops alongside plugin tools. NOT exposed
6
+ * to client agents — only the AI inside toolLoop() calls these.
7
+ *
8
+ * Tools:
9
+ * - git_clone: Clone a Git repo
10
+ * - fs_list: List files at a path
11
+ * - fs_read: Read a file at a path
12
+ *
13
+ * All filesystem operations are scoped to ./tmp/gitops-clones/
14
+ * to prevent path traversal attacks.
15
+ */
16
+ import type { AITool, ToolExecutor } from './ai-provider.interface.js';
17
+ /**
18
+ * Validate that a relative path is safe and resolve it within the clones directory.
19
+ * Uses sanitizeRelativePath for traversal checks, then resolves to absolute.
20
+ * Exported for testing.
21
+ */
22
+ export declare function validatePathWithinClones(inputPath: string): string;
23
+ export declare function getInternalTools(): AITool[];
24
+ /**
25
+ * Create a ToolExecutor that handles internal agentic-loop tools.
26
+ * Designed to be passed as the fallbackExecutor to pluginManager.createToolExecutor().
27
+ */
28
+ export declare function createInternalToolExecutor(sessionId: string): ToolExecutor;
29
+ /**
30
+ * Remove session clone directories older than the TTL.
31
+ * Called at the start of each new remediate investigation.
32
+ * Non-blocking: runs cleanup in the background without delaying investigation.
33
+ */
34
+ export declare function cleanupOldClones(maxAgeMs?: number): void;
35
+ //# sourceMappingURL=internal-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal-tools.d.ts","sourceRoot":"","sources":["../../src/core/internal-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAcvE;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAUlE;AAkBD,wBAAgB,gBAAgB,IAAI,MAAM,EAAE,CAgD3C;AAmHD;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,CAoB1E;AAID;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,GAAE,MAAuB,GAAG,IAAI,CAoBxE"}
@@ -0,0 +1,286 @@
1
+ "use strict";
2
+ /**
3
+ * Internal Agentic-Loop Tools (PRD #407)
4
+ *
5
+ * Tools that run locally in the MCP server, available to the AI
6
+ * during investigation loops alongside plugin tools. NOT exposed
7
+ * to client agents — only the AI inside toolLoop() calls these.
8
+ *
9
+ * Tools:
10
+ * - git_clone: Clone a Git repo
11
+ * - fs_list: List files at a path
12
+ * - fs_read: Read a file at a path
13
+ *
14
+ * All filesystem operations are scoped to ./tmp/gitops-clones/
15
+ * to prevent path traversal attacks.
16
+ */
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
30
+ }) : function(o, v) {
31
+ o["default"] = v;
32
+ });
33
+ var __importStar = (this && this.__importStar) || (function () {
34
+ var ownKeys = function(o) {
35
+ ownKeys = Object.getOwnPropertyNames || function (o) {
36
+ var ar = [];
37
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
+ return ar;
39
+ };
40
+ return ownKeys(o);
41
+ };
42
+ return function (mod) {
43
+ if (mod && mod.__esModule) return mod;
44
+ var result = {};
45
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
+ __setModuleDefault(result, mod);
47
+ return result;
48
+ };
49
+ })();
50
+ Object.defineProperty(exports, "__esModule", { value: true });
51
+ exports.validatePathWithinClones = validatePathWithinClones;
52
+ exports.getInternalTools = getInternalTools;
53
+ exports.createInternalToolExecutor = createInternalToolExecutor;
54
+ exports.cleanupOldClones = cleanupOldClones;
55
+ const fs = __importStar(require("fs"));
56
+ const path = __importStar(require("path"));
57
+ const git_utils_js_1 = require("./git-utils.js");
58
+ const solution_utils_js_1 = require("./solution-utils.js");
59
+ const CLONES_SUBDIR = 'gitops-clones';
60
+ const MAX_FILE_SIZE = 100 * 1024; // 100KB
61
+ const DEFAULT_TTL_MS = 60 * 60 * 1000; // 1 hour
62
+ // ─── Path security ───
63
+ function getClonesDir() {
64
+ return path.resolve(process.cwd(), 'tmp', CLONES_SUBDIR);
65
+ }
66
+ /**
67
+ * Validate that a relative path is safe and resolve it within the clones directory.
68
+ * Uses sanitizeRelativePath for traversal checks, then resolves to absolute.
69
+ * Exported for testing.
70
+ */
71
+ function validatePathWithinClones(inputPath) {
72
+ // Decode URL-encoded characters (e.g., %2e%2e/ for ../) before validation
73
+ let decoded;
74
+ try {
75
+ decoded = decodeURIComponent(inputPath);
76
+ }
77
+ catch {
78
+ decoded = inputPath;
79
+ }
80
+ const sanitized = (0, git_utils_js_1.sanitizeRelativePath)(decoded);
81
+ return path.resolve(getClonesDir(), sanitized);
82
+ }
83
+ // ─── Repo name sanitization ───
84
+ function repoUrlToDirectoryName(repoUrl) {
85
+ try {
86
+ const url = new URL(repoUrl);
87
+ const repoPath = url.pathname
88
+ .replace(/^\//, '')
89
+ .replace(/\.git$/, '');
90
+ return (0, solution_utils_js_1.sanitizeIntentForLabel)(repoPath);
91
+ }
92
+ catch {
93
+ return (0, solution_utils_js_1.sanitizeIntentForLabel)(repoUrl.slice(0, 63));
94
+ }
95
+ }
96
+ // ─── Tool definitions ───
97
+ function getInternalTools() {
98
+ return [
99
+ {
100
+ name: 'git_clone',
101
+ description: 'Clone a Git repository. Returns a relative path to the cloned repo.',
102
+ inputSchema: {
103
+ type: 'object',
104
+ properties: {
105
+ repoUrl: {
106
+ type: 'string',
107
+ description: 'Repository URL (HTTPS)',
108
+ },
109
+ },
110
+ required: ['repoUrl'],
111
+ },
112
+ },
113
+ {
114
+ name: 'fs_list',
115
+ description: 'List files and directories at a relative path within the working directory.',
116
+ inputSchema: {
117
+ type: 'object',
118
+ properties: {
119
+ path: {
120
+ type: 'string',
121
+ description: 'Relative path to list',
122
+ },
123
+ },
124
+ required: ['path'],
125
+ },
126
+ },
127
+ {
128
+ name: 'fs_read',
129
+ description: 'Read file contents at a relative path within the working directory.',
130
+ inputSchema: {
131
+ type: 'object',
132
+ properties: {
133
+ path: {
134
+ type: 'string',
135
+ description: 'Relative path to file',
136
+ },
137
+ },
138
+ required: ['path'],
139
+ },
140
+ },
141
+ ];
142
+ }
143
+ // ─── Handlers ───
144
+ async function handleGitClone(args, sessionId) {
145
+ const repoUrl = args.repoUrl;
146
+ if (!repoUrl) {
147
+ return 'Error: repoUrl is required';
148
+ }
149
+ const repoName = repoUrlToDirectoryName(repoUrl);
150
+ const relativePath = path.join(sessionId, repoName);
151
+ const targetDir = path.join(getClonesDir(), relativePath);
152
+ if (fs.existsSync(targetDir)) {
153
+ return { localPath: relativePath, message: 'Repository already cloned' };
154
+ }
155
+ const parentDir = path.dirname(targetDir);
156
+ if (!fs.existsSync(parentDir)) {
157
+ fs.mkdirSync(parentDir, { recursive: true });
158
+ }
159
+ try {
160
+ const result = await (0, git_utils_js_1.cloneRepo)(repoUrl, targetDir, { depth: 1 });
161
+ return { localPath: relativePath, branch: result.branch };
162
+ }
163
+ catch (err) {
164
+ const message = err instanceof Error ? err.message : String(err);
165
+ return `Error cloning repository: ${(0, git_utils_js_1.scrubCredentials)(message)}`;
166
+ }
167
+ }
168
+ function handleFsList(args) {
169
+ const inputPath = args.path;
170
+ if (!inputPath) {
171
+ return 'Error: path is required';
172
+ }
173
+ let resolved;
174
+ try {
175
+ resolved = validatePathWithinClones(inputPath);
176
+ }
177
+ catch (err) {
178
+ return `Error: ${err instanceof Error ? err.message : String(err)}`;
179
+ }
180
+ if (!fs.existsSync(resolved)) {
181
+ return `Error: path does not exist: ${inputPath}`;
182
+ }
183
+ const stat = fs.statSync(resolved);
184
+ if (!stat.isDirectory()) {
185
+ return `Error: path is not a directory: ${inputPath}`;
186
+ }
187
+ const entries = fs.readdirSync(resolved, { withFileTypes: true });
188
+ return entries.map((entry) => ({
189
+ name: entry.name,
190
+ type: entry.isDirectory() ? 'directory' : 'file',
191
+ }));
192
+ }
193
+ function handleFsRead(args) {
194
+ const inputPath = args.path;
195
+ if (!inputPath) {
196
+ return 'Error: path is required';
197
+ }
198
+ let resolved;
199
+ try {
200
+ resolved = validatePathWithinClones(inputPath);
201
+ }
202
+ catch (err) {
203
+ return `Error: ${err instanceof Error ? err.message : String(err)}`;
204
+ }
205
+ if (!fs.existsSync(resolved)) {
206
+ return `Error: file does not exist: ${inputPath}`;
207
+ }
208
+ const stat = fs.statSync(resolved);
209
+ if (stat.isDirectory()) {
210
+ return `Error: path is a directory, not a file: ${inputPath}`;
211
+ }
212
+ // Binary detection: check first 8KB for null bytes
213
+ const detectBuffer = Buffer.alloc(Math.min(8192, stat.size));
214
+ const fd = fs.openSync(resolved, 'r');
215
+ try {
216
+ fs.readSync(fd, detectBuffer, 0, detectBuffer.length, 0);
217
+ }
218
+ finally {
219
+ fs.closeSync(fd);
220
+ }
221
+ if (detectBuffer.includes(0)) {
222
+ return 'Binary file, cannot display';
223
+ }
224
+ if (stat.size > MAX_FILE_SIZE) {
225
+ const buffer = Buffer.alloc(MAX_FILE_SIZE);
226
+ const fd = fs.openSync(resolved, 'r');
227
+ try {
228
+ const bytesRead = fs.readSync(fd, buffer, 0, MAX_FILE_SIZE, 0);
229
+ const content = buffer.toString('utf-8', 0, bytesRead);
230
+ return `${content}\n\n[Truncated: file exceeds ${MAX_FILE_SIZE / 1024}KB limit]`;
231
+ }
232
+ finally {
233
+ fs.closeSync(fd);
234
+ }
235
+ }
236
+ return fs.readFileSync(resolved, 'utf-8');
237
+ }
238
+ // ─── Combined executor ───
239
+ /**
240
+ * Create a ToolExecutor that handles internal agentic-loop tools.
241
+ * Designed to be passed as the fallbackExecutor to pluginManager.createToolExecutor().
242
+ */
243
+ function createInternalToolExecutor(sessionId) {
244
+ const handlers = {
245
+ git_clone: (args) => handleGitClone(args, sessionId),
246
+ fs_list: handleFsList,
247
+ fs_read: handleFsRead,
248
+ };
249
+ return async (toolName, input) => {
250
+ const handler = handlers[toolName];
251
+ if (!handler) {
252
+ return `Error: unknown internal tool: ${toolName}`;
253
+ }
254
+ if (typeof input !== 'object' || input === null || Array.isArray(input)) {
255
+ return 'Error: tool input must be an object';
256
+ }
257
+ return handler(input);
258
+ };
259
+ }
260
+ // ─── TTL cleanup ───
261
+ /**
262
+ * Remove session clone directories older than the TTL.
263
+ * Called at the start of each new remediate investigation.
264
+ * Non-blocking: runs cleanup in the background without delaying investigation.
265
+ */
266
+ function cleanupOldClones(maxAgeMs = DEFAULT_TTL_MS) {
267
+ const clonesDir = getClonesDir();
268
+ fs.promises.access(clonesDir).then(async () => {
269
+ const now = Date.now();
270
+ const entries = await fs.promises.readdir(clonesDir);
271
+ for (const entry of entries) {
272
+ const entryPath = path.join(clonesDir, entry);
273
+ try {
274
+ const stat = await fs.promises.stat(entryPath);
275
+ if (stat.isDirectory() && now - stat.mtimeMs > maxAgeMs) {
276
+ await fs.promises.rm(entryPath, { recursive: true, force: true });
277
+ }
278
+ }
279
+ catch {
280
+ // Ignore errors during cleanup (e.g., concurrent deletion)
281
+ }
282
+ }
283
+ }).catch(() => {
284
+ // Directory doesn't exist yet, nothing to clean
285
+ });
286
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * MCP Client Manager for dot-ai MCP Server Integration
3
+ *
4
+ * Connects to external MCP servers running in the cluster, discovers their tools,
5
+ * and makes them available to dot-ai operations (remediate, operate, query) via
6
+ * the attachTo routing mechanism.
7
+ *
8
+ * PRD #358: MCP Server Integration
9
+ */
10
+ import { McpServerConfig, DiscoveredMcpServer, McpServerStats, McpAttachableOperation } from './mcp-client-types';
11
+ import { Logger } from './error-handling';
12
+ import { AITool, ToolExecutor } from './ai-provider.interface';
13
+ /**
14
+ * Manages MCP server connections, tool discovery, and tool routing.
15
+ *
16
+ * Follows the same structural patterns as PluginManager but uses
17
+ * the MCP SDK (Client + StreamableHTTPClientTransport) instead of HTTP REST.
18
+ */
19
+ export declare class McpClientManager {
20
+ private readonly logger;
21
+ /** MCP SDK Client instances keyed by server name */
22
+ private readonly clients;
23
+ /** Transport instances keyed by server name (needed for cleanup) */
24
+ private readonly transports;
25
+ /** Discovered server metadata keyed by server name */
26
+ private readonly discoveredServers;
27
+ /** Maps namespaced tool name → server name for routing */
28
+ private readonly toolToServer;
29
+ constructor(logger: Logger);
30
+ /**
31
+ * Parse MCP server configuration from file.
32
+ *
33
+ * Reads from /etc/dot-ai-mcp/mcp-servers.json (mounted from ConfigMap in K8s).
34
+ * Returns empty array if file doesn't exist (MCP servers only work in-cluster).
35
+ * Throws on invalid JSON or malformed configuration.
36
+ */
37
+ static parseMcpServerConfig(): McpServerConfig[];
38
+ /**
39
+ * Discover all configured MCP servers.
40
+ *
41
+ * Connects to each server, performs MCP handshake, and discovers available tools.
42
+ * All servers must connect successfully — any failure throws McpDiscoveryError.
43
+ */
44
+ discoverMcpServers(configs: McpServerConfig[]): Promise<void>;
45
+ /**
46
+ * Connect to a single MCP server and discover its tools.
47
+ */
48
+ private connectAndDiscover;
49
+ /**
50
+ * Get tools available for a specific dot-ai operation, filtered by attachTo.
51
+ *
52
+ * Returns tools as AITool[] with namespaced names ({serverName}__{toolName}).
53
+ */
54
+ getToolsForOperation(operation: McpAttachableOperation): AITool[];
55
+ /**
56
+ * Get all discovered tools across all servers.
57
+ */
58
+ getAllDiscoveredTools(): AITool[];
59
+ /**
60
+ * Check if a tool name belongs to an MCP server (is namespaced).
61
+ */
62
+ isMcpTool(toolName: string): boolean;
63
+ /**
64
+ * Create a ToolExecutor that routes MCP tools to their servers.
65
+ *
66
+ * Returns a function compatible with toolLoop's toolExecutor parameter.
67
+ * MCP tools (namespaced) are routed to their MCP servers; non-MCP tools
68
+ * are routed to the optional fallback executor.
69
+ */
70
+ createToolExecutor(fallbackExecutor?: ToolExecutor): ToolExecutor;
71
+ /**
72
+ * Get statistics about MCP server connections.
73
+ */
74
+ getStats(): McpServerStats;
75
+ /**
76
+ * Get discovered server metadata.
77
+ */
78
+ getDiscoveredServers(): DiscoveredMcpServer[];
79
+ /**
80
+ * Close all MCP server connections.
81
+ */
82
+ close(): Promise<void>;
83
+ /**
84
+ * Convert an MCP tool definition to AITool format with namespaced name.
85
+ */
86
+ private convertToAITool;
87
+ }
88
+ //# sourceMappingURL=mcp-client-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-client-manager.d.ts","sourceRoot":"","sources":["../../src/core/mcp-client-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,EACL,eAAe,EAEf,mBAAmB,EACnB,cAAc,EAEd,sBAAsB,EACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAW/D;;;;;GAKG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,oDAAoD;IACpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;IAE1D,oEAAoE;IACpE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyD;IAEpF,sDAAsD;IACtD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA+C;IAEjF,0DAA0D;IAC1D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkC;gBAEnD,MAAM,EAAE,MAAM;IAI1B;;;;;;OAMG;IACH,MAAM,CAAC,oBAAoB,IAAI,eAAe,EAAE;IA+DhD;;;;;OAKG;IACG,kBAAkB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAwCnE;;OAEG;YACW,kBAAkB;IAmGhC;;;;OAIG;IACH,oBAAoB,CAAC,SAAS,EAAE,sBAAsB,GAAG,MAAM,EAAE;IAoBjE;;OAEG;IACH,qBAAqB,IAAI,MAAM,EAAE;IAejC;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAIpC;;;;;;OAMG;IACH,kBAAkB,CAAC,gBAAgB,CAAC,EAAE,YAAY,GAAG,YAAY;IA6DjE;;OAEG;IACH,QAAQ,IAAI,cAAc;IAQ1B;;OAEG;IACH,oBAAoB,IAAI,mBAAmB,EAAE;IAI7C;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB5B;;OAEG;IACH,OAAO,CAAC,eAAe;CAYxB"}