opencode-gitlab-duo-agentic 0.2.6 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +169 -397
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -135,8 +135,8 @@ async function resolveRootNamespaceId(client, namespaceId) {
|
|
|
135
135
|
}
|
|
136
136
|
async function readGitConfig(cwd) {
|
|
137
137
|
const gitPath = path.join(cwd, ".git");
|
|
138
|
-
const
|
|
139
|
-
if (
|
|
138
|
+
const stat = await fs.stat(gitPath);
|
|
139
|
+
if (stat.isDirectory()) {
|
|
140
140
|
return fs.readFile(path.join(gitPath, "config"), "utf8");
|
|
141
141
|
}
|
|
142
142
|
const content = await fs.readFile(gitPath, "utf8");
|
|
@@ -415,7 +415,7 @@ var AsyncQueue = class {
|
|
|
415
415
|
const value = this.#values.shift();
|
|
416
416
|
if (value !== void 0) return Promise.resolve(value);
|
|
417
417
|
if (this.#closed) return Promise.resolve(null);
|
|
418
|
-
return new Promise((
|
|
418
|
+
return new Promise((resolve) => this.#waiters.push(resolve));
|
|
419
419
|
}
|
|
420
420
|
close() {
|
|
421
421
|
this.#closed = true;
|
|
@@ -572,7 +572,7 @@ var WorkflowWebSocketClient = class {
|
|
|
572
572
|
async connect(url, headers) {
|
|
573
573
|
const socket = new WebSocket(url, { headers });
|
|
574
574
|
this.#socket = socket;
|
|
575
|
-
await new Promise((
|
|
575
|
+
await new Promise((resolve, reject) => {
|
|
576
576
|
const timeout = setTimeout(() => {
|
|
577
577
|
cleanup();
|
|
578
578
|
socket.close(1e3);
|
|
@@ -585,7 +585,7 @@ var WorkflowWebSocketClient = class {
|
|
|
585
585
|
};
|
|
586
586
|
const onOpen = () => {
|
|
587
587
|
cleanup();
|
|
588
|
-
|
|
588
|
+
resolve();
|
|
589
589
|
};
|
|
590
590
|
const onError = (error) => {
|
|
591
591
|
cleanup();
|
|
@@ -650,375 +650,131 @@ function decodeSocketMessage(data) {
|
|
|
650
650
|
return void 0;
|
|
651
651
|
}
|
|
652
652
|
|
|
653
|
-
// src/workflow/
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
if (!Array.isArray(value)) return [];
|
|
665
|
-
return value.filter((v) => typeof v === "string");
|
|
666
|
-
}
|
|
667
|
-
var ToolExecutor = class {
|
|
668
|
-
#cwd;
|
|
669
|
-
#client;
|
|
670
|
-
constructor(cwd, client) {
|
|
671
|
-
this.#cwd = cwd;
|
|
672
|
-
this.#client = client;
|
|
673
|
-
}
|
|
674
|
-
/**
|
|
675
|
-
* Execute a tool request using Duo tool names and arg formats.
|
|
676
|
-
* This handles checkpoint-based tool requests where tool_info contains
|
|
677
|
-
* Duo-native names (read_file, edit_file, etc.) and Duo-native arg keys
|
|
678
|
-
* (file_path, old_str, new_str, etc.).
|
|
679
|
-
*/
|
|
680
|
-
async executeDuoTool(toolName, args) {
|
|
681
|
-
try {
|
|
682
|
-
switch (toolName) {
|
|
683
|
-
case "read_file": {
|
|
684
|
-
const filePath = asString(args.file_path) ?? asString(args.filepath) ?? asString(args.filePath) ?? asString(args.path);
|
|
685
|
-
if (!filePath) return { response: "", error: "read_file: missing file_path" };
|
|
686
|
-
return this.#read({ filePath, offset: asNumber(args.offset), limit: asNumber(args.limit) });
|
|
687
|
-
}
|
|
688
|
-
case "read_files": {
|
|
689
|
-
const paths = asStringArray(args.file_paths);
|
|
690
|
-
if (paths.length === 0) return { response: "", error: "read_files: missing file_paths" };
|
|
691
|
-
return this.#readMultiple(paths);
|
|
692
|
-
}
|
|
693
|
-
case "create_file_with_contents": {
|
|
694
|
-
const filePath = asString(args.file_path);
|
|
695
|
-
const content = asString(args.contents);
|
|
696
|
-
if (!filePath || content === void 0) return { response: "", error: "create_file: missing file_path or contents" };
|
|
697
|
-
return this.#write({ filePath, content });
|
|
698
|
-
}
|
|
699
|
-
case "edit_file": {
|
|
700
|
-
const filePath = asString(args.file_path);
|
|
701
|
-
const oldString = asString(args.old_str);
|
|
702
|
-
const newString = asString(args.new_str);
|
|
703
|
-
if (!filePath || oldString === void 0 || newString === void 0) {
|
|
704
|
-
return { response: "", error: "edit_file: missing file_path, old_str or new_str" };
|
|
705
|
-
}
|
|
706
|
-
return this.#edit({ filePath, oldString, newString });
|
|
707
|
-
}
|
|
708
|
-
case "list_dir": {
|
|
709
|
-
const directory = asString(args.directory) ?? ".";
|
|
710
|
-
return this.#read({ filePath: directory });
|
|
711
|
-
}
|
|
712
|
-
case "find_files": {
|
|
713
|
-
const pattern = asString(args.name_pattern);
|
|
714
|
-
if (!pattern) return { response: "", error: "find_files: missing name_pattern" };
|
|
715
|
-
return this.#glob({ pattern });
|
|
716
|
-
}
|
|
717
|
-
case "grep": {
|
|
718
|
-
const pattern = asString(args.pattern);
|
|
719
|
-
if (!pattern) return { response: "", error: "grep: missing pattern" };
|
|
720
|
-
return this.#grep({
|
|
721
|
-
pattern,
|
|
722
|
-
path: asString(args.search_directory),
|
|
723
|
-
caseInsensitive: Boolean(args.case_insensitive)
|
|
724
|
-
});
|
|
725
|
-
}
|
|
726
|
-
case "mkdir": {
|
|
727
|
-
const dir = asString(args.directory_path);
|
|
728
|
-
if (!dir) return { response: "", error: "mkdir: missing directory_path" };
|
|
729
|
-
return this.#mkdir(dir);
|
|
730
|
-
}
|
|
731
|
-
case "shell_command": {
|
|
732
|
-
const command = asString(args.command);
|
|
733
|
-
if (!command) return { response: "", error: "shell_command: missing command" };
|
|
734
|
-
return this.#bash({ command });
|
|
735
|
-
}
|
|
736
|
-
case "run_command": {
|
|
737
|
-
const program = asString(args.program);
|
|
738
|
-
if (!program) return { response: "", error: "run_command: missing program" };
|
|
739
|
-
const parts = [program];
|
|
740
|
-
const flags = args.flags;
|
|
741
|
-
if (Array.isArray(flags)) parts.push(...flags.map(String));
|
|
742
|
-
const cmdArgs = args.arguments;
|
|
743
|
-
if (Array.isArray(cmdArgs)) parts.push(...cmdArgs.map(String));
|
|
744
|
-
return this.#bash({ command: parts.join(" ") });
|
|
745
|
-
}
|
|
746
|
-
case "run_git_command": {
|
|
747
|
-
const command = asString(args.command);
|
|
748
|
-
if (!command) return { response: "", error: "run_git_command: missing command" };
|
|
749
|
-
const extra = Array.isArray(args.args) ? args.args.map(String).join(" ") : asString(args.args);
|
|
750
|
-
const gitCmd = extra ? `git ${command} ${extra}` : `git ${command}`;
|
|
751
|
-
return this.#bash({ command: gitCmd });
|
|
752
|
-
}
|
|
753
|
-
case "gitlab_api_request": {
|
|
754
|
-
const method = asString(args.method) ?? "GET";
|
|
755
|
-
const path3 = asString(args.path);
|
|
756
|
-
if (!path3) return { response: "", error: "gitlab_api_request: missing path" };
|
|
757
|
-
return this.#httpRequest({ method, path: path3, body: asString(args.body) });
|
|
758
|
-
}
|
|
759
|
-
default: {
|
|
760
|
-
console.error(`[tool-executor] unknown Duo tool: ${toolName}, args: ${JSON.stringify(args).slice(0, 300)}`);
|
|
761
|
-
return { response: "", error: `Unknown Duo tool: ${toolName}` };
|
|
762
|
-
}
|
|
653
|
+
// src/workflow/action-mapper.ts
|
|
654
|
+
function mapActionToToolRequest(action) {
|
|
655
|
+
const requestId = action.requestID;
|
|
656
|
+
if (!requestId) return null;
|
|
657
|
+
if (action.runMCPTool) {
|
|
658
|
+
let parsedArgs;
|
|
659
|
+
if (typeof action.runMCPTool.args === "string") {
|
|
660
|
+
try {
|
|
661
|
+
parsedArgs = JSON.parse(action.runMCPTool.args);
|
|
662
|
+
} catch {
|
|
663
|
+
parsedArgs = {};
|
|
763
664
|
}
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
return { response: "", error: err instanceof Error ? err.message : String(err) };
|
|
665
|
+
} else {
|
|
666
|
+
parsedArgs = {};
|
|
767
667
|
}
|
|
668
|
+
return { requestId, toolName: action.runMCPTool.name, args: parsedArgs };
|
|
768
669
|
}
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
filePath: action.runReadFile.filepath,
|
|
778
|
-
offset: action.runReadFile.offset,
|
|
779
|
-
limit: action.runReadFile.limit
|
|
780
|
-
});
|
|
781
|
-
}
|
|
782
|
-
if (action.runReadFiles) {
|
|
783
|
-
return this.#readMultiple(action.runReadFiles.filepaths);
|
|
784
|
-
}
|
|
785
|
-
if (action.runWriteFile) {
|
|
786
|
-
return this.#write({
|
|
787
|
-
filePath: action.runWriteFile.filepath,
|
|
788
|
-
content: action.runWriteFile.contents
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
if (action.runEditFile) {
|
|
792
|
-
return this.#edit({
|
|
793
|
-
filePath: action.runEditFile.filepath,
|
|
794
|
-
oldString: action.runEditFile.oldString,
|
|
795
|
-
newString: action.runEditFile.newString
|
|
796
|
-
});
|
|
797
|
-
}
|
|
798
|
-
if (action.runShellCommand) {
|
|
799
|
-
return this.#bash({ command: action.runShellCommand.command });
|
|
800
|
-
}
|
|
801
|
-
if (action.runCommand) {
|
|
802
|
-
const parts = [action.runCommand.program];
|
|
803
|
-
if (action.runCommand.flags) parts.push(...action.runCommand.flags);
|
|
804
|
-
if (action.runCommand.arguments) parts.push(...action.runCommand.arguments);
|
|
805
|
-
return this.#bash({ command: parts.join(" ") });
|
|
806
|
-
}
|
|
807
|
-
if (action.runGitCommand) {
|
|
808
|
-
const cmd = action.runGitCommand.arguments ? `git ${action.runGitCommand.command} ${action.runGitCommand.arguments}` : `git ${action.runGitCommand.command}`;
|
|
809
|
-
return this.#bash({ command: cmd });
|
|
810
|
-
}
|
|
811
|
-
if (action.listDirectory) {
|
|
812
|
-
return this.#read({ filePath: action.listDirectory.directory });
|
|
813
|
-
}
|
|
814
|
-
if (action.grep) {
|
|
815
|
-
return this.#grep({
|
|
816
|
-
pattern: action.grep.pattern,
|
|
817
|
-
path: action.grep.search_directory,
|
|
818
|
-
caseInsensitive: action.grep.case_insensitive
|
|
819
|
-
});
|
|
820
|
-
}
|
|
821
|
-
if (action.findFiles) {
|
|
822
|
-
return this.#glob({ pattern: action.findFiles.name_pattern });
|
|
823
|
-
}
|
|
824
|
-
if (action.mkdir) {
|
|
825
|
-
return this.#mkdir(action.mkdir.directory_path);
|
|
826
|
-
}
|
|
827
|
-
if (action.runMCPTool) {
|
|
828
|
-
return this.#executeMcpTool(action.runMCPTool.name, action.runMCPTool.args);
|
|
829
|
-
}
|
|
830
|
-
if (action.runHTTPRequest) {
|
|
831
|
-
return this.#httpRequest(action.runHTTPRequest);
|
|
670
|
+
if (action.runReadFile) {
|
|
671
|
+
return {
|
|
672
|
+
requestId,
|
|
673
|
+
toolName: "read_file",
|
|
674
|
+
args: {
|
|
675
|
+
file_path: action.runReadFile.filepath,
|
|
676
|
+
offset: action.runReadFile.offset,
|
|
677
|
+
limit: action.runReadFile.limit
|
|
832
678
|
}
|
|
833
|
-
|
|
834
|
-
console.error(`[tool-executor] unhandled action, keys: ${JSON.stringify(keys)}, raw: ${JSON.stringify(action).slice(0, 500)}`);
|
|
835
|
-
return { response: "", error: `Unknown tool action (keys: ${keys.join(", ")})` };
|
|
836
|
-
} catch (err) {
|
|
837
|
-
console.error(`[tool-executor] execution error:`, err);
|
|
838
|
-
return { response: "", error: err instanceof Error ? err.message : String(err) };
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
/**
|
|
842
|
-
* Execute an MCP tool by name (for runMCPTool actions).
|
|
843
|
-
* Routes to the appropriate handler based on tool name.
|
|
844
|
-
*/
|
|
845
|
-
async #executeMcpTool(name, argsJson) {
|
|
846
|
-
const args = argsJson ? JSON.parse(argsJson) : {};
|
|
847
|
-
switch (name) {
|
|
848
|
-
case "bash":
|
|
849
|
-
return this.#bash(args);
|
|
850
|
-
case "read":
|
|
851
|
-
return this.#read(args);
|
|
852
|
-
case "edit":
|
|
853
|
-
return this.#edit(args);
|
|
854
|
-
case "write":
|
|
855
|
-
return this.#write(args);
|
|
856
|
-
case "glob":
|
|
857
|
-
return this.#glob(args);
|
|
858
|
-
case "grep":
|
|
859
|
-
return this.#grep(args);
|
|
860
|
-
default:
|
|
861
|
-
return { response: "", error: `Unknown MCP tool: ${name}` };
|
|
862
|
-
}
|
|
679
|
+
};
|
|
863
680
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
const output = [stdout, stderr].filter(Boolean).join("\n");
|
|
871
|
-
if (error) {
|
|
872
|
-
res({ response: output, error: error.message });
|
|
873
|
-
return;
|
|
874
|
-
}
|
|
875
|
-
res({ response: output });
|
|
876
|
-
});
|
|
877
|
-
});
|
|
681
|
+
if (action.runReadFiles) {
|
|
682
|
+
return {
|
|
683
|
+
requestId,
|
|
684
|
+
toolName: "read_files",
|
|
685
|
+
args: { file_paths: action.runReadFiles.filepaths ?? [] }
|
|
686
|
+
};
|
|
878
687
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
return { response: lines.join("\n") };
|
|
688
|
+
if (action.runWriteFile) {
|
|
689
|
+
return {
|
|
690
|
+
requestId,
|
|
691
|
+
toolName: "create_file_with_contents",
|
|
692
|
+
args: {
|
|
693
|
+
file_path: action.runWriteFile.filepath,
|
|
694
|
+
contents: action.runWriteFile.contents
|
|
887
695
|
}
|
|
888
|
-
|
|
889
|
-
const allLines = content.split("\n");
|
|
890
|
-
const offset = Math.max((args.offset ?? 1) - 1, 0);
|
|
891
|
-
const limit = args.limit ?? 2e3;
|
|
892
|
-
const slice = allLines.slice(offset, offset + limit);
|
|
893
|
-
const numbered = slice.map((line, i) => `${offset + i + 1}: ${line}`);
|
|
894
|
-
return { response: numbered.join("\n") };
|
|
895
|
-
} catch (err) {
|
|
896
|
-
return { response: "", error: err instanceof Error ? err.message : String(err) };
|
|
897
|
-
}
|
|
696
|
+
};
|
|
898
697
|
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
results.push(`--- ${fp} ---
|
|
908
|
-
${result.response}`);
|
|
698
|
+
if (action.runEditFile) {
|
|
699
|
+
return {
|
|
700
|
+
requestId,
|
|
701
|
+
toolName: "edit_file",
|
|
702
|
+
args: {
|
|
703
|
+
file_path: action.runEditFile.filepath,
|
|
704
|
+
old_str: action.runEditFile.oldString,
|
|
705
|
+
new_str: action.runEditFile.newString
|
|
909
706
|
}
|
|
910
|
-
}
|
|
911
|
-
return { response: results.join("\n\n") };
|
|
707
|
+
};
|
|
912
708
|
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
return { response: "", error: "oldString not found in content" };
|
|
920
|
-
}
|
|
921
|
-
content = content.replaceAll(args.oldString, args.newString);
|
|
922
|
-
} else {
|
|
923
|
-
const idx = content.indexOf(args.oldString);
|
|
924
|
-
if (idx === -1) {
|
|
925
|
-
return { response: "", error: "oldString not found in content" };
|
|
926
|
-
}
|
|
927
|
-
const secondIdx = content.indexOf(args.oldString, idx + 1);
|
|
928
|
-
if (secondIdx !== -1) {
|
|
929
|
-
return { response: "", error: "Found multiple matches for oldString. Provide more surrounding lines to identify the correct match." };
|
|
930
|
-
}
|
|
931
|
-
content = content.slice(0, idx) + args.newString + content.slice(idx + args.oldString.length);
|
|
932
|
-
}
|
|
933
|
-
await writeFile(filepath, content, "utf-8");
|
|
934
|
-
return { response: `Successfully edited ${args.filePath}` };
|
|
935
|
-
} catch (err) {
|
|
936
|
-
return { response: "", error: err instanceof Error ? err.message : String(err) };
|
|
937
|
-
}
|
|
709
|
+
if (action.findFiles) {
|
|
710
|
+
return {
|
|
711
|
+
requestId,
|
|
712
|
+
toolName: "find_files",
|
|
713
|
+
args: { name_pattern: action.findFiles.name_pattern }
|
|
714
|
+
};
|
|
938
715
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
} catch (err) {
|
|
946
|
-
return { response: "", error: err instanceof Error ? err.message : String(err) };
|
|
947
|
-
}
|
|
716
|
+
if (action.listDirectory) {
|
|
717
|
+
return {
|
|
718
|
+
requestId,
|
|
719
|
+
toolName: "list_dir",
|
|
720
|
+
args: { directory: action.listDirectory.directory }
|
|
721
|
+
};
|
|
948
722
|
}
|
|
949
|
-
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
if (error && !stdout) {
|
|
955
|
-
exec(`ls -1 ${args.pattern} 2>/dev/null | head -200`, { cwd, timeout: 3e4 }, (_err, out) => {
|
|
956
|
-
res({ response: out.trim() || "No matches found" });
|
|
957
|
-
});
|
|
958
|
-
return;
|
|
959
|
-
}
|
|
960
|
-
const lines = stdout.trim().split("\n").filter(Boolean);
|
|
961
|
-
res({ response: lines.length > 0 ? lines.join("\n") : "No matches found" });
|
|
962
|
-
});
|
|
963
|
-
});
|
|
723
|
+
if (action.grep) {
|
|
724
|
+
const args = { pattern: action.grep.pattern };
|
|
725
|
+
if (action.grep.search_directory) args.search_directory = action.grep.search_directory;
|
|
726
|
+
if (action.grep.case_insensitive !== void 0) args.case_insensitive = action.grep.case_insensitive;
|
|
727
|
+
return { requestId, toolName: "grep", args };
|
|
964
728
|
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
const rgCommand = `rg --line-number --no-heading ${caseFlag} ${includeFlag} '${escapedPattern}' . 2>/dev/null | head -500`;
|
|
972
|
-
exec(rgCommand, { cwd, timeout: 3e4, maxBuffer: 5 * 1024 * 1024 }, (_error, stdout) => {
|
|
973
|
-
if (stdout.trim()) {
|
|
974
|
-
res({ response: stdout.trim() });
|
|
975
|
-
return;
|
|
976
|
-
}
|
|
977
|
-
const grepCaseFlag = args.caseInsensitive ? "-i" : "";
|
|
978
|
-
const grepInclude = args.include ? `--include='${args.include}'` : "";
|
|
979
|
-
const grepCommand = `grep -rn ${grepCaseFlag} ${grepInclude} '${escapedPattern}' . 2>/dev/null | head -500`;
|
|
980
|
-
exec(grepCommand, { cwd, timeout: 3e4, maxBuffer: 5 * 1024 * 1024 }, (_err, out) => {
|
|
981
|
-
res({ response: out.trim() || "No matches found" });
|
|
982
|
-
});
|
|
983
|
-
});
|
|
984
|
-
});
|
|
729
|
+
if (action.mkdir) {
|
|
730
|
+
return {
|
|
731
|
+
requestId,
|
|
732
|
+
toolName: "mkdir",
|
|
733
|
+
args: { directory_path: action.mkdir.directory_path }
|
|
734
|
+
};
|
|
985
735
|
}
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
}
|
|
992
|
-
return { response: "", error: err instanceof Error ? err.message : String(err) };
|
|
993
|
-
}
|
|
736
|
+
if (action.runShellCommand) {
|
|
737
|
+
return {
|
|
738
|
+
requestId,
|
|
739
|
+
toolName: "shell_command",
|
|
740
|
+
args: { command: action.runShellCommand.command }
|
|
741
|
+
};
|
|
994
742
|
}
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
};
|
|
1004
|
-
if (args.body) {
|
|
1005
|
-
headers["content-type"] = "application/json";
|
|
743
|
+
if (action.runCommand) {
|
|
744
|
+
return {
|
|
745
|
+
requestId,
|
|
746
|
+
toolName: "run_command",
|
|
747
|
+
args: {
|
|
748
|
+
program: action.runCommand.program,
|
|
749
|
+
flags: action.runCommand.flags,
|
|
750
|
+
arguments: action.runCommand.arguments
|
|
1006
751
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
if (action.runGitCommand) {
|
|
755
|
+
return {
|
|
756
|
+
requestId,
|
|
757
|
+
toolName: "run_git_command",
|
|
758
|
+
args: {
|
|
759
|
+
repository_url: action.runGitCommand.repository_url ?? "",
|
|
760
|
+
command: action.runGitCommand.command,
|
|
761
|
+
args: action.runGitCommand.arguments
|
|
1015
762
|
}
|
|
1016
|
-
|
|
1017
|
-
} catch (err) {
|
|
1018
|
-
return { response: "", error: err instanceof Error ? err.message : String(err) };
|
|
1019
|
-
}
|
|
763
|
+
};
|
|
1020
764
|
}
|
|
1021
|
-
|
|
765
|
+
if (action.runHTTPRequest) {
|
|
766
|
+
return {
|
|
767
|
+
requestId,
|
|
768
|
+
toolName: "gitlab_api_request",
|
|
769
|
+
args: {
|
|
770
|
+
method: action.runHTTPRequest.method,
|
|
771
|
+
path: action.runHTTPRequest.path,
|
|
772
|
+
body: action.runHTTPRequest.body
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
return null;
|
|
777
|
+
}
|
|
1022
778
|
|
|
1023
779
|
// src/workflow/session.ts
|
|
1024
780
|
var WorkflowSession = class {
|
|
@@ -1031,7 +787,6 @@ var WorkflowSession = class {
|
|
|
1031
787
|
#rootNamespaceId;
|
|
1032
788
|
#checkpoint = createCheckpointState();
|
|
1033
789
|
#toolsConfig;
|
|
1034
|
-
#toolExecutor;
|
|
1035
790
|
#socket;
|
|
1036
791
|
#queue;
|
|
1037
792
|
#startRequestSent = false;
|
|
@@ -1040,7 +795,6 @@ var WorkflowSession = class {
|
|
|
1040
795
|
this.#tokenService = new WorkflowTokenService(client);
|
|
1041
796
|
this.#modelId = modelId;
|
|
1042
797
|
this.#cwd = cwd;
|
|
1043
|
-
this.#toolExecutor = new ToolExecutor(cwd, client);
|
|
1044
798
|
}
|
|
1045
799
|
/**
|
|
1046
800
|
* Opt-in: override the server-side system prompt and/or register MCP tools.
|
|
@@ -1171,24 +925,18 @@ var WorkflowSession = class {
|
|
|
1171
925
|
}
|
|
1172
926
|
return;
|
|
1173
927
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
928
|
+
const toolAction = action;
|
|
929
|
+
const mapped = mapActionToToolRequest(toolAction);
|
|
930
|
+
if (mapped) {
|
|
931
|
+
console.error(`[duo-workflow] ws tool request: ${mapped.toolName} requestId=${mapped.requestId}`);
|
|
932
|
+
queue.push({
|
|
933
|
+
type: "tool-request",
|
|
934
|
+
requestId: mapped.requestId,
|
|
935
|
+
toolName: mapped.toolName,
|
|
936
|
+
args: mapped.args
|
|
937
|
+
});
|
|
1177
938
|
}
|
|
1178
939
|
}
|
|
1179
|
-
async #executeStandaloneAction(action) {
|
|
1180
|
-
if (!action.requestID || !this.#socket) return;
|
|
1181
|
-
const result = await this.#toolExecutor.executeAction(action);
|
|
1182
|
-
this.#socket.send({
|
|
1183
|
-
actionResponse: {
|
|
1184
|
-
requestID: action.requestID,
|
|
1185
|
-
plainTextResponse: {
|
|
1186
|
-
response: result.response,
|
|
1187
|
-
error: result.error ?? ""
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
});
|
|
1191
|
-
}
|
|
1192
940
|
// ---------------------------------------------------------------------------
|
|
1193
941
|
// Private: connection management
|
|
1194
942
|
// ---------------------------------------------------------------------------
|
|
@@ -1270,21 +1018,21 @@ function extractToolResults(prompt) {
|
|
|
1270
1018
|
const toolName = String(p.toolName ?? "");
|
|
1271
1019
|
if (!toolCallId) continue;
|
|
1272
1020
|
const { output, error } = parseToolResultOutput(p);
|
|
1273
|
-
const finalError = error ??
|
|
1021
|
+
const finalError = error ?? asString(p.error) ?? asString(p.errorText);
|
|
1274
1022
|
results.push({ toolCallId, toolName, output, error: finalError });
|
|
1275
1023
|
}
|
|
1276
1024
|
if (p.type === "tool-error") {
|
|
1277
1025
|
const toolCallId = String(p.toolCallId ?? "");
|
|
1278
1026
|
const toolName = String(p.toolName ?? "");
|
|
1279
1027
|
const errorValue = p.error ?? p.errorText ?? p.message;
|
|
1280
|
-
const error =
|
|
1028
|
+
const error = asString(errorValue) ?? String(errorValue ?? "");
|
|
1281
1029
|
results.push({ toolCallId, toolName, output: "", error });
|
|
1282
1030
|
}
|
|
1283
1031
|
}
|
|
1284
1032
|
}
|
|
1285
1033
|
return results;
|
|
1286
1034
|
}
|
|
1287
|
-
function
|
|
1035
|
+
function asString(value) {
|
|
1288
1036
|
return typeof value === "string" ? value : void 0;
|
|
1289
1037
|
}
|
|
1290
1038
|
function parseToolResultOutput(part) {
|
|
@@ -1309,7 +1057,7 @@ function parseToolResultOutput(part) {
|
|
|
1309
1057
|
}
|
|
1310
1058
|
if (resultField !== void 0) {
|
|
1311
1059
|
const output = typeof resultField === "string" ? resultField : JSON.stringify(resultField);
|
|
1312
|
-
const error = isPlainObject(resultField) ?
|
|
1060
|
+
const error = isPlainObject(resultField) ? asString(resultField.error) : void 0;
|
|
1313
1061
|
return { output, error };
|
|
1314
1062
|
}
|
|
1315
1063
|
return { output: "" };
|
|
@@ -1377,14 +1125,14 @@ function isPlainObject(value) {
|
|
|
1377
1125
|
function mapDuoToolRequest(toolName, args) {
|
|
1378
1126
|
switch (toolName) {
|
|
1379
1127
|
case "list_dir": {
|
|
1380
|
-
const directory =
|
|
1128
|
+
const directory = asString2(args.directory) ?? ".";
|
|
1381
1129
|
return {
|
|
1382
1130
|
toolName: "bash",
|
|
1383
1131
|
args: { command: `ls -la ${shellQuote(directory)}`, description: "List directory contents", workdir: "." }
|
|
1384
1132
|
};
|
|
1385
1133
|
}
|
|
1386
1134
|
case "read_file": {
|
|
1387
|
-
const filePath =
|
|
1135
|
+
const filePath = asString2(args.file_path) ?? asString2(args.filepath) ?? asString2(args.filePath) ?? asString2(args.path);
|
|
1388
1136
|
if (!filePath) return { toolName, args };
|
|
1389
1137
|
const mapped = { filePath };
|
|
1390
1138
|
if (typeof args.offset === "number") mapped.offset = args.offset;
|
|
@@ -1392,32 +1140,32 @@ function mapDuoToolRequest(toolName, args) {
|
|
|
1392
1140
|
return { toolName: "read", args: mapped };
|
|
1393
1141
|
}
|
|
1394
1142
|
case "read_files": {
|
|
1395
|
-
const filePaths =
|
|
1143
|
+
const filePaths = asStringArray(args.file_paths);
|
|
1396
1144
|
if (filePaths.length === 0) return { toolName, args };
|
|
1397
1145
|
return filePaths.map((fp) => ({ toolName: "read", args: { filePath: fp } }));
|
|
1398
1146
|
}
|
|
1399
1147
|
case "create_file_with_contents": {
|
|
1400
|
-
const filePath =
|
|
1401
|
-
const content =
|
|
1148
|
+
const filePath = asString2(args.file_path);
|
|
1149
|
+
const content = asString2(args.contents);
|
|
1402
1150
|
if (!filePath || content === void 0) return { toolName, args };
|
|
1403
1151
|
return { toolName: "write", args: { filePath, content } };
|
|
1404
1152
|
}
|
|
1405
1153
|
case "edit_file": {
|
|
1406
|
-
const filePath =
|
|
1407
|
-
const oldString =
|
|
1408
|
-
const newString =
|
|
1154
|
+
const filePath = asString2(args.file_path);
|
|
1155
|
+
const oldString = asString2(args.old_str);
|
|
1156
|
+
const newString = asString2(args.new_str);
|
|
1409
1157
|
if (!filePath || oldString === void 0 || newString === void 0) return { toolName, args };
|
|
1410
1158
|
return { toolName: "edit", args: { filePath, oldString, newString } };
|
|
1411
1159
|
}
|
|
1412
1160
|
case "find_files": {
|
|
1413
|
-
const pattern =
|
|
1161
|
+
const pattern = asString2(args.name_pattern);
|
|
1414
1162
|
if (!pattern) return { toolName, args };
|
|
1415
1163
|
return { toolName: "glob", args: { pattern } };
|
|
1416
1164
|
}
|
|
1417
1165
|
case "grep": {
|
|
1418
|
-
const pattern =
|
|
1166
|
+
const pattern = asString2(args.pattern);
|
|
1419
1167
|
if (!pattern) return { toolName, args };
|
|
1420
|
-
const searchDir =
|
|
1168
|
+
const searchDir = asString2(args.search_directory);
|
|
1421
1169
|
const caseInsensitive = Boolean(args.case_insensitive);
|
|
1422
1170
|
const normalizedPattern = caseInsensitive && !pattern.startsWith("(?i)") ? `(?i)${pattern}` : pattern;
|
|
1423
1171
|
const mapped = { pattern: normalizedPattern };
|
|
@@ -1425,43 +1173,67 @@ function mapDuoToolRequest(toolName, args) {
|
|
|
1425
1173
|
return { toolName: "grep", args: mapped };
|
|
1426
1174
|
}
|
|
1427
1175
|
case "mkdir": {
|
|
1428
|
-
const directory =
|
|
1176
|
+
const directory = asString2(args.directory_path);
|
|
1429
1177
|
if (!directory) return { toolName, args };
|
|
1430
1178
|
return { toolName: "bash", args: { command: `mkdir -p ${shellQuote(directory)}`, description: "Create directory", workdir: "." } };
|
|
1431
1179
|
}
|
|
1432
1180
|
case "shell_command": {
|
|
1433
|
-
const command =
|
|
1181
|
+
const command = asString2(args.command);
|
|
1434
1182
|
if (!command) return { toolName, args };
|
|
1435
1183
|
return { toolName: "bash", args: { command, description: "Run shell command", workdir: "." } };
|
|
1436
1184
|
}
|
|
1437
1185
|
case "run_command": {
|
|
1438
|
-
const program =
|
|
1186
|
+
const program = asString2(args.program);
|
|
1439
1187
|
if (program) {
|
|
1440
1188
|
const parts = [shellQuote(program)];
|
|
1441
1189
|
if (Array.isArray(args.flags)) parts.push(...args.flags.map((f) => shellQuote(String(f))));
|
|
1442
1190
|
if (Array.isArray(args.arguments)) parts.push(...args.arguments.map((a) => shellQuote(String(a))));
|
|
1443
1191
|
return { toolName: "bash", args: { command: parts.join(" "), description: "Run command", workdir: "." } };
|
|
1444
1192
|
}
|
|
1445
|
-
const command =
|
|
1193
|
+
const command = asString2(args.command);
|
|
1446
1194
|
if (!command) return { toolName, args };
|
|
1447
1195
|
return { toolName: "bash", args: { command, description: "Run command", workdir: "." } };
|
|
1448
1196
|
}
|
|
1449
1197
|
case "run_git_command": {
|
|
1450
|
-
const command =
|
|
1198
|
+
const command = asString2(args.command);
|
|
1451
1199
|
if (!command) return { toolName, args };
|
|
1452
1200
|
const rawArgs = args.args;
|
|
1453
|
-
const extraArgs = Array.isArray(rawArgs) ? rawArgs.map((v) => shellQuote(String(v))).join(" ") :
|
|
1201
|
+
const extraArgs = Array.isArray(rawArgs) ? rawArgs.map((v) => shellQuote(String(v))).join(" ") : asString2(rawArgs);
|
|
1454
1202
|
const gitCmd = extraArgs ? `git ${shellQuote(command)} ${extraArgs}` : `git ${shellQuote(command)}`;
|
|
1455
1203
|
return { toolName: "bash", args: { command: gitCmd, description: "Run git command", workdir: "." } };
|
|
1456
1204
|
}
|
|
1205
|
+
case "gitlab_api_request": {
|
|
1206
|
+
const method = asString2(args.method) ?? "GET";
|
|
1207
|
+
const apiPath = asString2(args.path);
|
|
1208
|
+
if (!apiPath) return { toolName, args };
|
|
1209
|
+
const body = asString2(args.body);
|
|
1210
|
+
const curlParts = [
|
|
1211
|
+
"curl",
|
|
1212
|
+
"-s",
|
|
1213
|
+
"-X",
|
|
1214
|
+
method,
|
|
1215
|
+
"-H",
|
|
1216
|
+
"'Authorization: Bearer $GITLAB_TOKEN'",
|
|
1217
|
+
"-H",
|
|
1218
|
+
"'Content-Type: application/json'"
|
|
1219
|
+
];
|
|
1220
|
+
if (body) {
|
|
1221
|
+
curlParts.push("-d", shellQuote(body));
|
|
1222
|
+
}
|
|
1223
|
+
curlParts.push(shellQuote(`$GITLAB_INSTANCE_URL/api/v4/${apiPath}`));
|
|
1224
|
+
return {
|
|
1225
|
+
toolName: "bash",
|
|
1226
|
+
args: { command: curlParts.join(" "), description: `GitLab API: ${method} ${apiPath}`, workdir: "." }
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1457
1229
|
default:
|
|
1458
1230
|
return { toolName, args };
|
|
1459
1231
|
}
|
|
1460
1232
|
}
|
|
1461
|
-
function
|
|
1233
|
+
function asString2(value) {
|
|
1462
1234
|
return typeof value === "string" ? value : void 0;
|
|
1463
1235
|
}
|
|
1464
|
-
function
|
|
1236
|
+
function asStringArray(value) {
|
|
1465
1237
|
if (!Array.isArray(value)) return [];
|
|
1466
1238
|
return value.filter((v) => typeof v === "string");
|
|
1467
1239
|
}
|