partnercore-proxy 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +54 -0
- package/README.md +2 -0
- package/dist/al/extension-manager.js +10 -50
- package/dist/al/extension-manager.js.map +1 -1
- package/dist/al/index.js +2 -18
- package/dist/al/index.js.map +1 -1
- package/dist/al/language-server.js +9 -46
- package/dist/al/language-server.js.map +1 -1
- package/dist/cli.js +36 -68
- package/dist/cli.js.map +1 -1
- package/dist/cloud/index.js +1 -17
- package/dist/cloud/index.js.map +1 -1
- package/dist/cloud/relay-client.js +5 -12
- package/dist/cloud/relay-client.js.map +1 -1
- package/dist/config/index.js +2 -18
- package/dist/config/index.js.map +1 -1
- package/dist/config/loader.js +8 -47
- package/dist/config/loader.js.map +1 -1
- package/dist/config/types.js +1 -4
- package/dist/config/types.js.map +1 -1
- package/dist/container/bc-container.js +8 -45
- package/dist/container/bc-container.js.map +1 -1
- package/dist/git/git-operations.js +6 -10
- package/dist/git/git-operations.js.map +1 -1
- package/dist/index.js +6 -22
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.js +1 -17
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/server.js +10 -14
- package/dist/mcp/server.js.map +1 -1
- package/dist/memory/project-memory.js +5 -42
- package/dist/memory/project-memory.js.map +1 -1
- package/dist/router/index.js +1 -17
- package/dist/router/index.js.map +1 -1
- package/dist/router/tool-router.js +109 -146
- package/dist/router/tool-router.js.map +1 -1
- package/dist/utils/index.js +2 -18
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logger.js +8 -16
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/security.js +10 -54
- package/dist/utils/security.js.map +1 -1
- package/package.json +4 -3
|
@@ -1,58 +1,22 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Tool Router
|
|
4
3
|
*
|
|
5
4
|
* Routes MCP tool calls to either local AL LSP or cloud PartnerCore server
|
|
6
5
|
* based on tool routing configuration.
|
|
7
6
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
o[k2] = m[k];
|
|
18
|
-
}));
|
|
19
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
-
}) : function(o, v) {
|
|
22
|
-
o["default"] = v;
|
|
23
|
-
});
|
|
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
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
-
exports.ToolRouter = void 0;
|
|
43
|
-
const fs = __importStar(require("fs"));
|
|
44
|
-
const path = __importStar(require("path"));
|
|
45
|
-
const types_js_1 = require("../config/types.js");
|
|
46
|
-
const project_memory_js_1 = require("../memory/project-memory.js");
|
|
47
|
-
const bc_container_js_1 = require("../container/bc-container.js");
|
|
48
|
-
const git_operations_js_1 = require("../git/git-operations.js");
|
|
49
|
-
const logger_js_1 = require("../utils/logger.js");
|
|
50
|
-
const loader_js_1 = require("../config/loader.js");
|
|
51
|
-
const security_js_1 = require("../utils/security.js");
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import { DEFAULT_TOOL_ROUTING } from '../config/types.js';
|
|
10
|
+
import { ProjectMemory } from '../memory/project-memory.js';
|
|
11
|
+
import { BCContainerManager } from '../container/bc-container.js';
|
|
12
|
+
import { GitOperations } from '../git/git-operations.js';
|
|
13
|
+
import { getLogger } from '../utils/logger.js';
|
|
14
|
+
import { findALWorkspace } from '../config/loader.js';
|
|
15
|
+
import { sanitizePath, validateToolArgs, sanitizeString, SecurityError, ValidationError, } from '../utils/security.js';
|
|
52
16
|
/**
|
|
53
17
|
* Tool Router
|
|
54
18
|
*/
|
|
55
|
-
class ToolRouter {
|
|
19
|
+
export class ToolRouter {
|
|
56
20
|
routing;
|
|
57
21
|
alServer = null;
|
|
58
22
|
cloudClient = null;
|
|
@@ -60,14 +24,14 @@ class ToolRouter {
|
|
|
60
24
|
containerManager = null;
|
|
61
25
|
gitOperations = null;
|
|
62
26
|
workspaceRoot;
|
|
63
|
-
logger =
|
|
27
|
+
logger = getLogger();
|
|
64
28
|
localToolDefinitions = [];
|
|
65
29
|
constructor(workspaceRoot, customRouting) {
|
|
66
30
|
// Use provided workspace or null (will be detected dynamically)
|
|
67
31
|
this.workspaceRoot = workspaceRoot || null;
|
|
68
32
|
this.routing = new Map();
|
|
69
33
|
// Apply default routing
|
|
70
|
-
for (const rule of
|
|
34
|
+
for (const rule of DEFAULT_TOOL_ROUTING) {
|
|
71
35
|
this.routing.set(rule.tool, rule.route);
|
|
72
36
|
}
|
|
73
37
|
// Apply custom routing overrides
|
|
@@ -101,7 +65,7 @@ class ToolRouter {
|
|
|
101
65
|
}
|
|
102
66
|
// Try to detect from file path if provided
|
|
103
67
|
if (filePath) {
|
|
104
|
-
const detected =
|
|
68
|
+
const detected = findALWorkspace(path.resolve(filePath));
|
|
105
69
|
if (detected) {
|
|
106
70
|
this.workspaceRoot = detected;
|
|
107
71
|
this.logger.debug(`Auto-detected workspace: ${detected} from file: ${filePath}`);
|
|
@@ -109,7 +73,7 @@ class ToolRouter {
|
|
|
109
73
|
}
|
|
110
74
|
}
|
|
111
75
|
// Try to detect from current working directory
|
|
112
|
-
const detected =
|
|
76
|
+
const detected = findALWorkspace();
|
|
113
77
|
if (detected) {
|
|
114
78
|
this.workspaceRoot = detected;
|
|
115
79
|
this.logger.debug(`Auto-detected workspace: ${detected} from current directory`);
|
|
@@ -308,7 +272,7 @@ class ToolRouter {
|
|
|
308
272
|
}
|
|
309
273
|
catch (error) {
|
|
310
274
|
// Don't log full stack traces for expected errors
|
|
311
|
-
if (error instanceof
|
|
275
|
+
if (error instanceof SecurityError) {
|
|
312
276
|
this.logger.warn(`Security error in ${call.name}: ${error.code}`);
|
|
313
277
|
return {
|
|
314
278
|
success: false,
|
|
@@ -316,7 +280,7 @@ class ToolRouter {
|
|
|
316
280
|
isError: true,
|
|
317
281
|
};
|
|
318
282
|
}
|
|
319
|
-
if (error instanceof
|
|
283
|
+
if (error instanceof ValidationError) {
|
|
320
284
|
this.logger.debug(`Validation error in ${call.name}: ${error.message}`);
|
|
321
285
|
return {
|
|
322
286
|
success: false,
|
|
@@ -424,7 +388,7 @@ class ToolRouter {
|
|
|
424
388
|
if (!this.alServer) {
|
|
425
389
|
return { success: false, content: 'AL Language Server not initialized', isError: true };
|
|
426
390
|
}
|
|
427
|
-
|
|
391
|
+
validateToolArgs(args, ['uri'], { uri: 'string' });
|
|
428
392
|
const uri = args['uri'];
|
|
429
393
|
// If line/character provided, create a point range; otherwise use full diagnostics
|
|
430
394
|
let range;
|
|
@@ -474,7 +438,7 @@ class ToolRouter {
|
|
|
474
438
|
if (!this.alServer) {
|
|
475
439
|
return { success: false, content: 'AL Language Server not initialized', isError: true };
|
|
476
440
|
}
|
|
477
|
-
|
|
441
|
+
validateToolArgs(args, ['uri', 'line', 'character'], {
|
|
478
442
|
uri: 'string',
|
|
479
443
|
line: 'number',
|
|
480
444
|
character: 'number'
|
|
@@ -506,7 +470,7 @@ class ToolRouter {
|
|
|
506
470
|
if (!this.alServer) {
|
|
507
471
|
return { success: false, content: 'AL Language Server not initialized', isError: true };
|
|
508
472
|
}
|
|
509
|
-
|
|
473
|
+
validateToolArgs(args, ['uri'], { uri: 'string' });
|
|
510
474
|
const uri = args['uri'];
|
|
511
475
|
const tabSize = args['tabSize'];
|
|
512
476
|
const insertSpaces = args['insertSpaces'];
|
|
@@ -536,7 +500,7 @@ class ToolRouter {
|
|
|
536
500
|
if (args['apply'] === true) {
|
|
537
501
|
const filePath = this.uriToPath(uri);
|
|
538
502
|
const workspaceRoot = this.getWorkspaceRoot(filePath);
|
|
539
|
-
const safePath =
|
|
503
|
+
const safePath = sanitizePath(filePath, workspaceRoot);
|
|
540
504
|
let content = fs.readFileSync(safePath, 'utf-8');
|
|
541
505
|
const lines = content.split('\n');
|
|
542
506
|
// Apply edits in reverse order to maintain positions
|
|
@@ -589,7 +553,7 @@ class ToolRouter {
|
|
|
589
553
|
if (!this.alServer) {
|
|
590
554
|
return { success: false, content: 'AL Language Server not initialized', isError: true };
|
|
591
555
|
}
|
|
592
|
-
|
|
556
|
+
validateToolArgs(args, ['uri', 'line', 'character'], {
|
|
593
557
|
uri: 'string', line: 'number', character: 'number'
|
|
594
558
|
});
|
|
595
559
|
const uri = args['uri'];
|
|
@@ -611,7 +575,7 @@ class ToolRouter {
|
|
|
611
575
|
if (!this.alServer) {
|
|
612
576
|
return { success: false, content: 'AL Language Server not initialized', isError: true };
|
|
613
577
|
}
|
|
614
|
-
|
|
578
|
+
validateToolArgs(args, ['uri'], { uri: 'string' });
|
|
615
579
|
const uri = args['uri'];
|
|
616
580
|
const ranges = await this.alServer.getFoldingRanges(uri);
|
|
617
581
|
return {
|
|
@@ -630,7 +594,7 @@ class ToolRouter {
|
|
|
630
594
|
if (!this.alServer) {
|
|
631
595
|
return { success: false, content: 'AL Language Server not initialized', isError: true };
|
|
632
596
|
}
|
|
633
|
-
|
|
597
|
+
validateToolArgs(args, ['uri', 'positions'], { uri: 'string' });
|
|
634
598
|
const uri = args['uri'];
|
|
635
599
|
const positions = args['positions'];
|
|
636
600
|
const ranges = await this.alServer.getSelectionRanges(uri, positions);
|
|
@@ -657,7 +621,7 @@ class ToolRouter {
|
|
|
657
621
|
if (!this.alServer) {
|
|
658
622
|
return { success: false, content: 'AL Language Server not initialized', isError: true };
|
|
659
623
|
}
|
|
660
|
-
|
|
624
|
+
validateToolArgs(args, ['uri', 'line', 'character'], {
|
|
661
625
|
uri: 'string', line: 'number', character: 'number'
|
|
662
626
|
});
|
|
663
627
|
const uri = args['uri'];
|
|
@@ -679,7 +643,7 @@ class ToolRouter {
|
|
|
679
643
|
if (!this.alServer) {
|
|
680
644
|
return { success: false, content: 'AL Language Server not initialized', isError: true };
|
|
681
645
|
}
|
|
682
|
-
|
|
646
|
+
validateToolArgs(args, ['uri', 'line', 'character'], {
|
|
683
647
|
uri: 'string', line: 'number', character: 'number'
|
|
684
648
|
});
|
|
685
649
|
const uri = args['uri'];
|
|
@@ -701,7 +665,7 @@ class ToolRouter {
|
|
|
701
665
|
if (!this.alServer) {
|
|
702
666
|
return { success: false, content: 'AL Language Server not initialized', isError: true };
|
|
703
667
|
}
|
|
704
|
-
|
|
668
|
+
validateToolArgs(args, ['uri', 'line', 'character', 'ch'], {
|
|
705
669
|
uri: 'string', line: 'number', character: 'number', ch: 'string'
|
|
706
670
|
});
|
|
707
671
|
const uri = args['uri'];
|
|
@@ -723,7 +687,7 @@ class ToolRouter {
|
|
|
723
687
|
if (!this.alServer) {
|
|
724
688
|
return { success: false, content: 'AL Language Server not initialized', isError: true };
|
|
725
689
|
}
|
|
726
|
-
|
|
690
|
+
validateToolArgs(args, ['uri'], { uri: 'string' });
|
|
727
691
|
const uri = args['uri'];
|
|
728
692
|
const resolve = args['resolve'];
|
|
729
693
|
let lenses = await this.alServer.getCodeLenses(uri);
|
|
@@ -746,7 +710,7 @@ class ToolRouter {
|
|
|
746
710
|
if (!this.alServer) {
|
|
747
711
|
return { success: false, content: 'AL Language Server not initialized', isError: true };
|
|
748
712
|
}
|
|
749
|
-
|
|
713
|
+
validateToolArgs(args, ['uri'], { uri: 'string' });
|
|
750
714
|
const uri = args['uri'];
|
|
751
715
|
const links = await this.alServer.getDocumentLinks(uri);
|
|
752
716
|
return {
|
|
@@ -765,7 +729,7 @@ class ToolRouter {
|
|
|
765
729
|
if (!this.alServer) {
|
|
766
730
|
return { success: false, content: 'AL Language Server not initialized', isError: true };
|
|
767
731
|
}
|
|
768
|
-
|
|
732
|
+
validateToolArgs(args, ['command'], { command: 'string' });
|
|
769
733
|
const command = args['command'];
|
|
770
734
|
const commandArgs = args['arguments'];
|
|
771
735
|
try {
|
|
@@ -785,7 +749,7 @@ class ToolRouter {
|
|
|
785
749
|
if (!this.alServer) {
|
|
786
750
|
return { success: false, content: 'AL Language Server not initialized', isError: true };
|
|
787
751
|
}
|
|
788
|
-
|
|
752
|
+
validateToolArgs(args, ['uri'], { uri: 'string' });
|
|
789
753
|
const uri = args['uri'];
|
|
790
754
|
// Check if range is provided
|
|
791
755
|
if (args['startLine'] !== undefined) {
|
|
@@ -829,7 +793,7 @@ class ToolRouter {
|
|
|
829
793
|
if (!this.alServer) {
|
|
830
794
|
return { success: false, content: 'AL Language Server not initialized', isError: true };
|
|
831
795
|
}
|
|
832
|
-
|
|
796
|
+
validateToolArgs(args, ['uri'], { uri: 'string' });
|
|
833
797
|
const uri = args['uri'];
|
|
834
798
|
await this.alServer.closeDocument(uri);
|
|
835
799
|
return { success: true, content: { message: `Document closed: ${uri}` } };
|
|
@@ -838,7 +802,7 @@ class ToolRouter {
|
|
|
838
802
|
if (!this.alServer) {
|
|
839
803
|
return { success: false, content: 'AL Language Server not initialized', isError: true };
|
|
840
804
|
}
|
|
841
|
-
|
|
805
|
+
validateToolArgs(args, ['uri'], { uri: 'string' });
|
|
842
806
|
const uri = args['uri'];
|
|
843
807
|
const text = args['text'];
|
|
844
808
|
await this.alServer.saveDocument(uri, text);
|
|
@@ -866,7 +830,7 @@ class ToolRouter {
|
|
|
866
830
|
if (!this.alServer) {
|
|
867
831
|
return { success: false, content: 'AL Language Server not initialized', isError: true };
|
|
868
832
|
}
|
|
869
|
-
|
|
833
|
+
validateToolArgs(args, ['uri', 'line', 'character'], {
|
|
870
834
|
uri: 'string', line: 'number', character: 'number'
|
|
871
835
|
});
|
|
872
836
|
const uri = args['uri'];
|
|
@@ -900,7 +864,7 @@ class ToolRouter {
|
|
|
900
864
|
if (!this.alServer) {
|
|
901
865
|
return { success: false, content: 'AL Language Server not initialized', isError: true };
|
|
902
866
|
}
|
|
903
|
-
|
|
867
|
+
validateToolArgs(args, ['uri', 'symbolName', 'content'], {
|
|
904
868
|
uri: 'string', symbolName: 'string', content: 'string'
|
|
905
869
|
});
|
|
906
870
|
const uri = args['uri'];
|
|
@@ -918,7 +882,7 @@ class ToolRouter {
|
|
|
918
882
|
// Read the file and insert before the symbol
|
|
919
883
|
const filePath = this.uriToPath(uri);
|
|
920
884
|
const workspaceRoot = this.getWorkspaceRoot(filePath);
|
|
921
|
-
const safePath =
|
|
885
|
+
const safePath = sanitizePath(filePath, workspaceRoot);
|
|
922
886
|
const fileContent = fs.readFileSync(safePath, 'utf-8');
|
|
923
887
|
const lines = fileContent.split('\n');
|
|
924
888
|
const insertLine = symbol.range.start.line;
|
|
@@ -934,7 +898,7 @@ class ToolRouter {
|
|
|
934
898
|
};
|
|
935
899
|
}
|
|
936
900
|
handleFindFile(args) {
|
|
937
|
-
|
|
901
|
+
validateToolArgs(args, ['pattern'], { pattern: 'string' });
|
|
938
902
|
const pattern = args['pattern'];
|
|
939
903
|
const directory = args['directory'] || '.';
|
|
940
904
|
const workspaceRoot = this.getWorkspaceRoot();
|
|
@@ -977,7 +941,7 @@ class ToolRouter {
|
|
|
977
941
|
};
|
|
978
942
|
}
|
|
979
943
|
handleReplaceContent(args) {
|
|
980
|
-
|
|
944
|
+
validateToolArgs(args, ['path', 'needle', 'replacement'], {
|
|
981
945
|
path: 'string', needle: 'string', replacement: 'string'
|
|
982
946
|
});
|
|
983
947
|
const filePath = args['path'];
|
|
@@ -986,7 +950,7 @@ class ToolRouter {
|
|
|
986
950
|
const mode = args['mode'] || 'literal';
|
|
987
951
|
const allowMultiple = args['allowMultiple'];
|
|
988
952
|
const workspaceRoot = this.getWorkspaceRoot(filePath);
|
|
989
|
-
const safePath =
|
|
953
|
+
const safePath = sanitizePath(filePath, workspaceRoot);
|
|
990
954
|
if (!fs.existsSync(safePath)) {
|
|
991
955
|
return { success: false, content: `File not found: ${filePath}`, isError: true };
|
|
992
956
|
}
|
|
@@ -1058,7 +1022,7 @@ class ToolRouter {
|
|
|
1058
1022
|
};
|
|
1059
1023
|
}
|
|
1060
1024
|
handleEditMemory(args) {
|
|
1061
|
-
|
|
1025
|
+
validateToolArgs(args, ['name', 'needle', 'replacement'], {
|
|
1062
1026
|
name: 'string', needle: 'string', replacement: 'string'
|
|
1063
1027
|
});
|
|
1064
1028
|
const name = args['name'];
|
|
@@ -1075,8 +1039,8 @@ class ToolRouter {
|
|
|
1075
1039
|
};
|
|
1076
1040
|
}
|
|
1077
1041
|
async handleGetExtensions(args) {
|
|
1078
|
-
|
|
1079
|
-
const containerName =
|
|
1042
|
+
validateToolArgs(args, ['containerName'], { containerName: 'string' });
|
|
1043
|
+
const containerName = sanitizeString(args['containerName']);
|
|
1080
1044
|
const manager = this.getContainerManager();
|
|
1081
1045
|
const result = await manager.getExtensions(containerName);
|
|
1082
1046
|
return {
|
|
@@ -1086,10 +1050,10 @@ class ToolRouter {
|
|
|
1086
1050
|
};
|
|
1087
1051
|
}
|
|
1088
1052
|
async handleUninstallApp(args) {
|
|
1089
|
-
|
|
1053
|
+
validateToolArgs(args, ['containerName', 'name'], {
|
|
1090
1054
|
containerName: 'string', name: 'string'
|
|
1091
1055
|
});
|
|
1092
|
-
const containerName =
|
|
1056
|
+
const containerName = sanitizeString(args['containerName']);
|
|
1093
1057
|
const manager = this.getContainerManager();
|
|
1094
1058
|
const result = await manager.uninstallApp(containerName, {
|
|
1095
1059
|
name: args['name'],
|
|
@@ -1108,8 +1072,8 @@ class ToolRouter {
|
|
|
1108
1072
|
};
|
|
1109
1073
|
}
|
|
1110
1074
|
async handleCompileWarnings(args) {
|
|
1111
|
-
|
|
1112
|
-
const containerName =
|
|
1075
|
+
validateToolArgs(args, ['containerName'], { containerName: 'string' });
|
|
1076
|
+
const containerName = sanitizeString(args['containerName']);
|
|
1113
1077
|
const manager = this.getContainerManager();
|
|
1114
1078
|
const result = await manager.compileWarningsOnly(containerName, {
|
|
1115
1079
|
appProjectFolder: args['appProjectFolder'],
|
|
@@ -1133,12 +1097,12 @@ class ToolRouter {
|
|
|
1133
1097
|
// All file operations are sandboxed to the workspace root for security
|
|
1134
1098
|
handleReadFile(args) {
|
|
1135
1099
|
// Validate required arguments
|
|
1136
|
-
|
|
1137
|
-
const filePath =
|
|
1100
|
+
validateToolArgs(args, ['path'], { path: 'string' });
|
|
1101
|
+
const filePath = sanitizeString(args['path']);
|
|
1138
1102
|
// Detect workspace dynamically if needed
|
|
1139
1103
|
const workspaceRoot = this.getWorkspaceRoot(filePath);
|
|
1140
1104
|
// Security: Sanitize path to prevent directory traversal
|
|
1141
|
-
const resolved =
|
|
1105
|
+
const resolved = sanitizePath(filePath, workspaceRoot);
|
|
1142
1106
|
if (!fs.existsSync(resolved)) {
|
|
1143
1107
|
return { success: false, content: `File not found: ${filePath}`, isError: true };
|
|
1144
1108
|
}
|
|
@@ -1156,13 +1120,13 @@ class ToolRouter {
|
|
|
1156
1120
|
}
|
|
1157
1121
|
handleWriteFile(args) {
|
|
1158
1122
|
// Validate required arguments
|
|
1159
|
-
|
|
1160
|
-
const filePath =
|
|
1123
|
+
validateToolArgs(args, ['path', 'content'], { path: 'string', content: 'string' });
|
|
1124
|
+
const filePath = sanitizeString(args['path']);
|
|
1161
1125
|
const content = args['content'];
|
|
1162
1126
|
// Detect workspace dynamically if needed
|
|
1163
1127
|
const workspaceRoot = this.getWorkspaceRoot(filePath);
|
|
1164
1128
|
// Security: Sanitize path to prevent directory traversal
|
|
1165
|
-
const resolved =
|
|
1129
|
+
const resolved = sanitizePath(filePath, workspaceRoot);
|
|
1166
1130
|
const dir = path.dirname(resolved);
|
|
1167
1131
|
// Security: Limit content size
|
|
1168
1132
|
const MAX_CONTENT_SIZE = 10 * 1024 * 1024; // 10MB
|
|
@@ -1177,13 +1141,13 @@ class ToolRouter {
|
|
|
1177
1141
|
}
|
|
1178
1142
|
handleListFiles(args) {
|
|
1179
1143
|
// Validate required arguments
|
|
1180
|
-
|
|
1181
|
-
const dirPath =
|
|
1182
|
-
const pattern = args['pattern'] ?
|
|
1144
|
+
validateToolArgs(args, ['path'], { path: 'string' });
|
|
1145
|
+
const dirPath = sanitizeString(args['path']);
|
|
1146
|
+
const pattern = args['pattern'] ? sanitizeString(args['pattern']) : undefined;
|
|
1183
1147
|
// Detect workspace dynamically if needed
|
|
1184
1148
|
const workspaceRoot = this.getWorkspaceRoot(dirPath);
|
|
1185
1149
|
// Security: Sanitize path to prevent directory traversal
|
|
1186
|
-
const resolved =
|
|
1150
|
+
const resolved = sanitizePath(dirPath, workspaceRoot);
|
|
1187
1151
|
if (!fs.existsSync(resolved)) {
|
|
1188
1152
|
return { success: false, content: `Directory not found: ${dirPath}`, isError: true };
|
|
1189
1153
|
}
|
|
@@ -1246,14 +1210,14 @@ class ToolRouter {
|
|
|
1246
1210
|
}
|
|
1247
1211
|
handleSearchFiles(args) {
|
|
1248
1212
|
// Validate required arguments
|
|
1249
|
-
|
|
1250
|
-
const dirPath =
|
|
1251
|
-
const query =
|
|
1252
|
-
const filePattern = args['filePattern'] ?
|
|
1213
|
+
validateToolArgs(args, ['path', 'query'], { path: 'string', query: 'string' });
|
|
1214
|
+
const dirPath = sanitizeString(args['path']);
|
|
1215
|
+
const query = sanitizeString(args['query'], 1000); // Limit query length
|
|
1216
|
+
const filePattern = args['filePattern'] ? sanitizeString(args['filePattern']) : '*.al';
|
|
1253
1217
|
// Detect workspace dynamically if needed
|
|
1254
1218
|
const workspaceRoot = this.getWorkspaceRoot(dirPath);
|
|
1255
1219
|
// Security: Sanitize path
|
|
1256
|
-
const resolved =
|
|
1220
|
+
const resolved = sanitizePath(dirPath, workspaceRoot);
|
|
1257
1221
|
const files = this.listFilesRecursive(resolved, filePattern);
|
|
1258
1222
|
const results = [];
|
|
1259
1223
|
const queryLower = query.toLowerCase();
|
|
@@ -1288,13 +1252,13 @@ class ToolRouter {
|
|
|
1288
1252
|
if (!this.alServer) {
|
|
1289
1253
|
return { success: false, content: 'AL Language Server not initialized', isError: true };
|
|
1290
1254
|
}
|
|
1291
|
-
|
|
1255
|
+
validateToolArgs(args, ['uri', 'line', 'character', 'newName'], {
|
|
1292
1256
|
uri: 'string', line: 'number', character: 'number', newName: 'string'
|
|
1293
1257
|
});
|
|
1294
1258
|
const uri = args['uri'];
|
|
1295
1259
|
const line = args['line'];
|
|
1296
1260
|
const character = args['character'];
|
|
1297
|
-
const newName =
|
|
1261
|
+
const newName = sanitizeString(args['newName']);
|
|
1298
1262
|
const workspaceEdit = await this.alServer.renameSymbol(uri, line, character, newName);
|
|
1299
1263
|
if (!workspaceEdit) {
|
|
1300
1264
|
return { success: false, content: 'Rename not available at this position', isError: true };
|
|
@@ -1351,11 +1315,11 @@ class ToolRouter {
|
|
|
1351
1315
|
if (!this.alServer) {
|
|
1352
1316
|
return { success: false, content: 'AL Language Server not initialized', isError: true };
|
|
1353
1317
|
}
|
|
1354
|
-
|
|
1318
|
+
validateToolArgs(args, ['uri', 'symbolName', 'content'], {
|
|
1355
1319
|
uri: 'string', symbolName: 'string', content: 'string'
|
|
1356
1320
|
});
|
|
1357
1321
|
const uri = args['uri'];
|
|
1358
|
-
const symbolName =
|
|
1322
|
+
const symbolName = sanitizeString(args['symbolName']);
|
|
1359
1323
|
const insertContent = args['content'];
|
|
1360
1324
|
const filePath = this.uriToPath(uri);
|
|
1361
1325
|
// Find the symbol
|
|
@@ -1386,11 +1350,11 @@ class ToolRouter {
|
|
|
1386
1350
|
if (!this.alServer) {
|
|
1387
1351
|
return { success: false, content: 'AL Language Server not initialized', isError: true };
|
|
1388
1352
|
}
|
|
1389
|
-
|
|
1353
|
+
validateToolArgs(args, ['uri', 'symbolName', 'newBody'], {
|
|
1390
1354
|
uri: 'string', symbolName: 'string', newBody: 'string'
|
|
1391
1355
|
});
|
|
1392
1356
|
const uri = args['uri'];
|
|
1393
|
-
const symbolName =
|
|
1357
|
+
const symbolName = sanitizeString(args['symbolName']);
|
|
1394
1358
|
const newBody = args['newBody'];
|
|
1395
1359
|
const filePath = this.uriToPath(uri);
|
|
1396
1360
|
// Find the symbol
|
|
@@ -1425,14 +1389,14 @@ class ToolRouter {
|
|
|
1425
1389
|
}
|
|
1426
1390
|
// ==================== Advanced File Operations ====================
|
|
1427
1391
|
handleDeleteLines(args) {
|
|
1428
|
-
|
|
1392
|
+
validateToolArgs(args, ['path', 'startLine', 'endLine'], {
|
|
1429
1393
|
path: 'string', startLine: 'number', endLine: 'number'
|
|
1430
1394
|
});
|
|
1431
|
-
const filePath =
|
|
1395
|
+
const filePath = sanitizeString(args['path']);
|
|
1432
1396
|
const startLine = args['startLine']; // 1-based
|
|
1433
1397
|
const endLine = args['endLine']; // 1-based
|
|
1434
1398
|
const workspaceRoot = this.getWorkspaceRoot(filePath);
|
|
1435
|
-
const resolved =
|
|
1399
|
+
const resolved = sanitizePath(filePath, workspaceRoot);
|
|
1436
1400
|
if (!fs.existsSync(resolved)) {
|
|
1437
1401
|
return { success: false, content: `File not found: ${filePath}`, isError: true };
|
|
1438
1402
|
}
|
|
@@ -1456,15 +1420,15 @@ class ToolRouter {
|
|
|
1456
1420
|
};
|
|
1457
1421
|
}
|
|
1458
1422
|
handleReplaceLines(args) {
|
|
1459
|
-
|
|
1423
|
+
validateToolArgs(args, ['path', 'startLine', 'endLine', 'newContent'], {
|
|
1460
1424
|
path: 'string', startLine: 'number', endLine: 'number', newContent: 'string'
|
|
1461
1425
|
});
|
|
1462
|
-
const filePath =
|
|
1426
|
+
const filePath = sanitizeString(args['path']);
|
|
1463
1427
|
const startLine = args['startLine']; // 1-based
|
|
1464
1428
|
const endLine = args['endLine']; // 1-based
|
|
1465
1429
|
const newContent = args['newContent'];
|
|
1466
1430
|
const workspaceRoot = this.getWorkspaceRoot(filePath);
|
|
1467
|
-
const resolved =
|
|
1431
|
+
const resolved = sanitizePath(filePath, workspaceRoot);
|
|
1468
1432
|
if (!fs.existsSync(resolved)) {
|
|
1469
1433
|
return { success: false, content: `File not found: ${filePath}`, isError: true };
|
|
1470
1434
|
}
|
|
@@ -1490,14 +1454,14 @@ class ToolRouter {
|
|
|
1490
1454
|
};
|
|
1491
1455
|
}
|
|
1492
1456
|
handleInsertAtLine(args) {
|
|
1493
|
-
|
|
1457
|
+
validateToolArgs(args, ['path', 'line', 'content'], {
|
|
1494
1458
|
path: 'string', line: 'number', content: 'string'
|
|
1495
1459
|
});
|
|
1496
|
-
const filePath =
|
|
1460
|
+
const filePath = sanitizeString(args['path']);
|
|
1497
1461
|
const lineNumber = args['line']; // 1-based
|
|
1498
1462
|
const insertContent = args['content'];
|
|
1499
1463
|
const workspaceRoot = this.getWorkspaceRoot(filePath);
|
|
1500
|
-
const resolved =
|
|
1464
|
+
const resolved = sanitizePath(filePath, workspaceRoot);
|
|
1501
1465
|
if (!fs.existsSync(resolved)) {
|
|
1502
1466
|
return { success: false, content: `File not found: ${filePath}`, isError: true };
|
|
1503
1467
|
}
|
|
@@ -1525,13 +1489,13 @@ class ToolRouter {
|
|
|
1525
1489
|
getProjectMemory() {
|
|
1526
1490
|
if (!this.projectMemory) {
|
|
1527
1491
|
const workspaceRoot = this.getWorkspaceRoot();
|
|
1528
|
-
this.projectMemory = new
|
|
1492
|
+
this.projectMemory = new ProjectMemory(workspaceRoot);
|
|
1529
1493
|
}
|
|
1530
1494
|
return this.projectMemory;
|
|
1531
1495
|
}
|
|
1532
1496
|
handleWriteMemory(args) {
|
|
1533
|
-
|
|
1534
|
-
const name =
|
|
1497
|
+
validateToolArgs(args, ['name', 'content'], { name: 'string', content: 'string' });
|
|
1498
|
+
const name = sanitizeString(args['name']);
|
|
1535
1499
|
const content = args['content'];
|
|
1536
1500
|
const tags = args['tags'];
|
|
1537
1501
|
const memory = this.getProjectMemory();
|
|
@@ -1550,8 +1514,8 @@ class ToolRouter {
|
|
|
1550
1514
|
};
|
|
1551
1515
|
}
|
|
1552
1516
|
handleReadMemory(args) {
|
|
1553
|
-
|
|
1554
|
-
const name =
|
|
1517
|
+
validateToolArgs(args, ['name'], { name: 'string' });
|
|
1518
|
+
const name = sanitizeString(args['name']);
|
|
1555
1519
|
const memory = this.getProjectMemory();
|
|
1556
1520
|
const result = memory.readMemory(name);
|
|
1557
1521
|
if (!result) {
|
|
@@ -1579,8 +1543,8 @@ class ToolRouter {
|
|
|
1579
1543
|
};
|
|
1580
1544
|
}
|
|
1581
1545
|
handleDeleteMemory(args) {
|
|
1582
|
-
|
|
1583
|
-
const name =
|
|
1546
|
+
validateToolArgs(args, ['name'], { name: 'string' });
|
|
1547
|
+
const name = sanitizeString(args['name']);
|
|
1584
1548
|
const memory = this.getProjectMemory();
|
|
1585
1549
|
const deleted = memory.deleteMemory(name);
|
|
1586
1550
|
if (!deleted) {
|
|
@@ -1595,7 +1559,7 @@ class ToolRouter {
|
|
|
1595
1559
|
getContainerManager() {
|
|
1596
1560
|
if (!this.containerManager) {
|
|
1597
1561
|
const workspaceRoot = this.getWorkspaceRoot();
|
|
1598
|
-
this.containerManager = new
|
|
1562
|
+
this.containerManager = new BCContainerManager(workspaceRoot);
|
|
1599
1563
|
}
|
|
1600
1564
|
return this.containerManager;
|
|
1601
1565
|
}
|
|
@@ -1617,8 +1581,8 @@ class ToolRouter {
|
|
|
1617
1581
|
};
|
|
1618
1582
|
}
|
|
1619
1583
|
async handleCompile(args) {
|
|
1620
|
-
|
|
1621
|
-
const containerName =
|
|
1584
|
+
validateToolArgs(args, ['containerName'], { containerName: 'string' });
|
|
1585
|
+
const containerName = sanitizeString(args['containerName']);
|
|
1622
1586
|
const manager = this.getContainerManager();
|
|
1623
1587
|
const result = await manager.compile(containerName, {
|
|
1624
1588
|
appProjectFolder: args['appProjectFolder'],
|
|
@@ -1637,8 +1601,8 @@ class ToolRouter {
|
|
|
1637
1601
|
};
|
|
1638
1602
|
}
|
|
1639
1603
|
async handlePublish(args) {
|
|
1640
|
-
|
|
1641
|
-
const containerName =
|
|
1604
|
+
validateToolArgs(args, ['containerName'], { containerName: 'string' });
|
|
1605
|
+
const containerName = sanitizeString(args['containerName']);
|
|
1642
1606
|
const manager = this.getContainerManager();
|
|
1643
1607
|
const result = await manager.publish(containerName, {
|
|
1644
1608
|
appFile: args['appFile'],
|
|
@@ -1653,8 +1617,8 @@ class ToolRouter {
|
|
|
1653
1617
|
};
|
|
1654
1618
|
}
|
|
1655
1619
|
async handleRunTests(args) {
|
|
1656
|
-
|
|
1657
|
-
const containerName =
|
|
1620
|
+
validateToolArgs(args, ['containerName'], { containerName: 'string' });
|
|
1621
|
+
const containerName = sanitizeString(args['containerName']);
|
|
1658
1622
|
const manager = this.getContainerManager();
|
|
1659
1623
|
const result = await manager.runTests(containerName, {
|
|
1660
1624
|
testCodeunit: args['testCodeunit'],
|
|
@@ -1677,8 +1641,8 @@ class ToolRouter {
|
|
|
1677
1641
|
};
|
|
1678
1642
|
}
|
|
1679
1643
|
async handleContainerLogs(args) {
|
|
1680
|
-
|
|
1681
|
-
const containerName =
|
|
1644
|
+
validateToolArgs(args, ['containerName'], { containerName: 'string' });
|
|
1645
|
+
const containerName = sanitizeString(args['containerName']);
|
|
1682
1646
|
const manager = this.getContainerManager();
|
|
1683
1647
|
const logs = await manager.getLogs(containerName, {
|
|
1684
1648
|
tail: args['tail'],
|
|
@@ -1690,8 +1654,8 @@ class ToolRouter {
|
|
|
1690
1654
|
};
|
|
1691
1655
|
}
|
|
1692
1656
|
async handleStartContainer(args) {
|
|
1693
|
-
|
|
1694
|
-
const containerName =
|
|
1657
|
+
validateToolArgs(args, ['containerName'], { containerName: 'string' });
|
|
1658
|
+
const containerName = sanitizeString(args['containerName']);
|
|
1695
1659
|
const manager = this.getContainerManager();
|
|
1696
1660
|
const result = await manager.startContainer(containerName);
|
|
1697
1661
|
return {
|
|
@@ -1701,8 +1665,8 @@ class ToolRouter {
|
|
|
1701
1665
|
};
|
|
1702
1666
|
}
|
|
1703
1667
|
async handleStopContainer(args) {
|
|
1704
|
-
|
|
1705
|
-
const containerName =
|
|
1668
|
+
validateToolArgs(args, ['containerName'], { containerName: 'string' });
|
|
1669
|
+
const containerName = sanitizeString(args['containerName']);
|
|
1706
1670
|
const manager = this.getContainerManager();
|
|
1707
1671
|
const result = await manager.stopContainer(containerName);
|
|
1708
1672
|
return {
|
|
@@ -1712,8 +1676,8 @@ class ToolRouter {
|
|
|
1712
1676
|
};
|
|
1713
1677
|
}
|
|
1714
1678
|
async handleRestartContainer(args) {
|
|
1715
|
-
|
|
1716
|
-
const containerName =
|
|
1679
|
+
validateToolArgs(args, ['containerName'], { containerName: 'string' });
|
|
1680
|
+
const containerName = sanitizeString(args['containerName']);
|
|
1717
1681
|
const manager = this.getContainerManager();
|
|
1718
1682
|
const result = await manager.restartContainer(containerName);
|
|
1719
1683
|
return {
|
|
@@ -1723,8 +1687,8 @@ class ToolRouter {
|
|
|
1723
1687
|
};
|
|
1724
1688
|
}
|
|
1725
1689
|
async handleDownloadSymbols(args) {
|
|
1726
|
-
|
|
1727
|
-
const containerName =
|
|
1690
|
+
validateToolArgs(args, ['containerName'], { containerName: 'string' });
|
|
1691
|
+
const containerName = sanitizeString(args['containerName']);
|
|
1728
1692
|
const manager = this.getContainerManager();
|
|
1729
1693
|
const result = await manager.downloadSymbols(containerName, args['targetFolder']);
|
|
1730
1694
|
return {
|
|
@@ -1734,8 +1698,8 @@ class ToolRouter {
|
|
|
1734
1698
|
};
|
|
1735
1699
|
}
|
|
1736
1700
|
async handleCreateContainer(args) {
|
|
1737
|
-
|
|
1738
|
-
const containerName =
|
|
1701
|
+
validateToolArgs(args, ['containerName'], { containerName: 'string' });
|
|
1702
|
+
const containerName = sanitizeString(args['containerName']);
|
|
1739
1703
|
const manager = this.getContainerManager();
|
|
1740
1704
|
// Build options from arguments
|
|
1741
1705
|
const options = {
|
|
@@ -1769,8 +1733,8 @@ class ToolRouter {
|
|
|
1769
1733
|
};
|
|
1770
1734
|
}
|
|
1771
1735
|
async handleRemoveContainer(args) {
|
|
1772
|
-
|
|
1773
|
-
const containerName =
|
|
1736
|
+
validateToolArgs(args, ['containerName'], { containerName: 'string' });
|
|
1737
|
+
const containerName = sanitizeString(args['containerName']);
|
|
1774
1738
|
const force = args['force'];
|
|
1775
1739
|
const manager = this.getContainerManager();
|
|
1776
1740
|
const result = await manager.removeContainer(containerName, force);
|
|
@@ -1784,7 +1748,7 @@ class ToolRouter {
|
|
|
1784
1748
|
getGitOperations() {
|
|
1785
1749
|
if (!this.gitOperations) {
|
|
1786
1750
|
const workspaceRoot = this.getWorkspaceRoot();
|
|
1787
|
-
this.gitOperations = new
|
|
1751
|
+
this.gitOperations = new GitOperations(workspaceRoot);
|
|
1788
1752
|
}
|
|
1789
1753
|
return this.gitOperations;
|
|
1790
1754
|
}
|
|
@@ -1809,7 +1773,7 @@ class ToolRouter {
|
|
|
1809
1773
|
};
|
|
1810
1774
|
}
|
|
1811
1775
|
async handleGitStage(args) {
|
|
1812
|
-
|
|
1776
|
+
validateToolArgs(args, ['paths'], {});
|
|
1813
1777
|
const git = this.getGitOperations();
|
|
1814
1778
|
const paths = args['paths'];
|
|
1815
1779
|
const result = await git.stage(paths);
|
|
@@ -1820,7 +1784,7 @@ class ToolRouter {
|
|
|
1820
1784
|
};
|
|
1821
1785
|
}
|
|
1822
1786
|
async handleGitCommit(args) {
|
|
1823
|
-
|
|
1787
|
+
validateToolArgs(args, ['message'], { message: 'string' });
|
|
1824
1788
|
const git = this.getGitOperations();
|
|
1825
1789
|
const message = args['message'];
|
|
1826
1790
|
const result = await git.commit(message, {
|
|
@@ -1865,9 +1829,9 @@ class ToolRouter {
|
|
|
1865
1829
|
};
|
|
1866
1830
|
}
|
|
1867
1831
|
async handleGitCheckout(args) {
|
|
1868
|
-
|
|
1832
|
+
validateToolArgs(args, ['target'], { target: 'string' });
|
|
1869
1833
|
const git = this.getGitOperations();
|
|
1870
|
-
const target =
|
|
1834
|
+
const target = sanitizeString(args['target']);
|
|
1871
1835
|
const result = await git.checkout(target, {
|
|
1872
1836
|
create: args['create'],
|
|
1873
1837
|
});
|
|
@@ -1929,7 +1893,7 @@ class ToolRouter {
|
|
|
1929
1893
|
}
|
|
1930
1894
|
// ==================== Getting Started Handler ====================
|
|
1931
1895
|
async handleGetStarted() {
|
|
1932
|
-
const workspace = this.workspaceRoot ||
|
|
1896
|
+
const workspace = this.workspaceRoot || findALWorkspace();
|
|
1933
1897
|
const hasWorkspace = !!workspace;
|
|
1934
1898
|
// Check AL Language Server status
|
|
1935
1899
|
const alServerReady = this.alServer !== null;
|
|
@@ -2975,5 +2939,4 @@ class ToolRouter {
|
|
|
2975
2939
|
];
|
|
2976
2940
|
}
|
|
2977
2941
|
}
|
|
2978
|
-
exports.ToolRouter = ToolRouter;
|
|
2979
2942
|
//# sourceMappingURL=tool-router.js.map
|