opencode-gitlab-duo-agentic 0.2.6 → 0.2.8
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 +199 -401
- 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,142 @@ 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
|
+
}
|
|
778
|
+
|
|
779
|
+
// src/utils/debug-log.ts
|
|
780
|
+
import { appendFileSync } from "fs";
|
|
781
|
+
var LOG = "/tmp/duo-workflow-debug.log";
|
|
782
|
+
function dlog(msg) {
|
|
783
|
+
try {
|
|
784
|
+
appendFileSync(LOG, `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
785
|
+
`);
|
|
786
|
+
} catch {
|
|
787
|
+
}
|
|
788
|
+
}
|
|
1022
789
|
|
|
1023
790
|
// src/workflow/session.ts
|
|
1024
791
|
var WorkflowSession = class {
|
|
@@ -1031,7 +798,6 @@ var WorkflowSession = class {
|
|
|
1031
798
|
#rootNamespaceId;
|
|
1032
799
|
#checkpoint = createCheckpointState();
|
|
1033
800
|
#toolsConfig;
|
|
1034
|
-
#toolExecutor;
|
|
1035
801
|
#socket;
|
|
1036
802
|
#queue;
|
|
1037
803
|
#startRequestSent = false;
|
|
@@ -1040,7 +806,6 @@ var WorkflowSession = class {
|
|
|
1040
806
|
this.#tokenService = new WorkflowTokenService(client);
|
|
1041
807
|
this.#modelId = modelId;
|
|
1042
808
|
this.#cwd = cwd;
|
|
1043
|
-
this.#toolExecutor = new ToolExecutor(cwd, client);
|
|
1044
809
|
}
|
|
1045
810
|
/**
|
|
1046
811
|
* Opt-in: override the server-side system prompt and/or register MCP tools.
|
|
@@ -1118,8 +883,11 @@ var WorkflowSession = class {
|
|
|
1118
883
|
* Send a tool result back to DWS on the existing connection.
|
|
1119
884
|
*/
|
|
1120
885
|
sendToolResult(requestId, output, error) {
|
|
1121
|
-
if (!this.#socket)
|
|
1122
|
-
|
|
886
|
+
if (!this.#socket) {
|
|
887
|
+
dlog(`sendToolResult: NOT CONNECTED reqId=${requestId}`);
|
|
888
|
+
throw new Error("Not connected");
|
|
889
|
+
}
|
|
890
|
+
dlog(`sendToolResult: reqId=${requestId} output=${output.length}b err=${error ?? "none"}`);
|
|
1123
891
|
this.#socket.send({
|
|
1124
892
|
actionResponse: {
|
|
1125
893
|
requestID: requestId,
|
|
@@ -1135,7 +903,10 @@ var WorkflowSession = class {
|
|
|
1135
903
|
* Returns null when the stream is closed (turn complete or connection lost).
|
|
1136
904
|
*/
|
|
1137
905
|
async waitForEvent() {
|
|
1138
|
-
if (!this.#queue)
|
|
906
|
+
if (!this.#queue) {
|
|
907
|
+
dlog("waitForEvent: queue=null");
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
1139
910
|
return this.#queue.shift();
|
|
1140
911
|
}
|
|
1141
912
|
/**
|
|
@@ -1171,24 +942,18 @@ var WorkflowSession = class {
|
|
|
1171
942
|
}
|
|
1172
943
|
return;
|
|
1173
944
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
945
|
+
const toolAction = action;
|
|
946
|
+
const mapped = mapActionToToolRequest(toolAction);
|
|
947
|
+
if (mapped) {
|
|
948
|
+
dlog(`action: standalone ${mapped.toolName} reqId=${mapped.requestId}`);
|
|
949
|
+
queue.push({
|
|
950
|
+
type: "tool-request",
|
|
951
|
+
requestId: mapped.requestId,
|
|
952
|
+
toolName: mapped.toolName,
|
|
953
|
+
args: mapped.args
|
|
954
|
+
});
|
|
1177
955
|
}
|
|
1178
956
|
}
|
|
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
957
|
// ---------------------------------------------------------------------------
|
|
1193
958
|
// Private: connection management
|
|
1194
959
|
// ---------------------------------------------------------------------------
|
|
@@ -1270,21 +1035,21 @@ function extractToolResults(prompt) {
|
|
|
1270
1035
|
const toolName = String(p.toolName ?? "");
|
|
1271
1036
|
if (!toolCallId) continue;
|
|
1272
1037
|
const { output, error } = parseToolResultOutput(p);
|
|
1273
|
-
const finalError = error ??
|
|
1038
|
+
const finalError = error ?? asString(p.error) ?? asString(p.errorText);
|
|
1274
1039
|
results.push({ toolCallId, toolName, output, error: finalError });
|
|
1275
1040
|
}
|
|
1276
1041
|
if (p.type === "tool-error") {
|
|
1277
1042
|
const toolCallId = String(p.toolCallId ?? "");
|
|
1278
1043
|
const toolName = String(p.toolName ?? "");
|
|
1279
1044
|
const errorValue = p.error ?? p.errorText ?? p.message;
|
|
1280
|
-
const error =
|
|
1045
|
+
const error = asString(errorValue) ?? String(errorValue ?? "");
|
|
1281
1046
|
results.push({ toolCallId, toolName, output: "", error });
|
|
1282
1047
|
}
|
|
1283
1048
|
}
|
|
1284
1049
|
}
|
|
1285
1050
|
return results;
|
|
1286
1051
|
}
|
|
1287
|
-
function
|
|
1052
|
+
function asString(value) {
|
|
1288
1053
|
return typeof value === "string" ? value : void 0;
|
|
1289
1054
|
}
|
|
1290
1055
|
function parseToolResultOutput(part) {
|
|
@@ -1309,7 +1074,7 @@ function parseToolResultOutput(part) {
|
|
|
1309
1074
|
}
|
|
1310
1075
|
if (resultField !== void 0) {
|
|
1311
1076
|
const output = typeof resultField === "string" ? resultField : JSON.stringify(resultField);
|
|
1312
|
-
const error = isPlainObject(resultField) ?
|
|
1077
|
+
const error = isPlainObject(resultField) ? asString(resultField.error) : void 0;
|
|
1313
1078
|
return { output, error };
|
|
1314
1079
|
}
|
|
1315
1080
|
return { output: "" };
|
|
@@ -1377,14 +1142,14 @@ function isPlainObject(value) {
|
|
|
1377
1142
|
function mapDuoToolRequest(toolName, args) {
|
|
1378
1143
|
switch (toolName) {
|
|
1379
1144
|
case "list_dir": {
|
|
1380
|
-
const directory =
|
|
1145
|
+
const directory = asString2(args.directory) ?? ".";
|
|
1381
1146
|
return {
|
|
1382
1147
|
toolName: "bash",
|
|
1383
1148
|
args: { command: `ls -la ${shellQuote(directory)}`, description: "List directory contents", workdir: "." }
|
|
1384
1149
|
};
|
|
1385
1150
|
}
|
|
1386
1151
|
case "read_file": {
|
|
1387
|
-
const filePath =
|
|
1152
|
+
const filePath = asString2(args.file_path) ?? asString2(args.filepath) ?? asString2(args.filePath) ?? asString2(args.path);
|
|
1388
1153
|
if (!filePath) return { toolName, args };
|
|
1389
1154
|
const mapped = { filePath };
|
|
1390
1155
|
if (typeof args.offset === "number") mapped.offset = args.offset;
|
|
@@ -1392,32 +1157,32 @@ function mapDuoToolRequest(toolName, args) {
|
|
|
1392
1157
|
return { toolName: "read", args: mapped };
|
|
1393
1158
|
}
|
|
1394
1159
|
case "read_files": {
|
|
1395
|
-
const filePaths =
|
|
1160
|
+
const filePaths = asStringArray(args.file_paths);
|
|
1396
1161
|
if (filePaths.length === 0) return { toolName, args };
|
|
1397
1162
|
return filePaths.map((fp) => ({ toolName: "read", args: { filePath: fp } }));
|
|
1398
1163
|
}
|
|
1399
1164
|
case "create_file_with_contents": {
|
|
1400
|
-
const filePath =
|
|
1401
|
-
const content =
|
|
1165
|
+
const filePath = asString2(args.file_path);
|
|
1166
|
+
const content = asString2(args.contents);
|
|
1402
1167
|
if (!filePath || content === void 0) return { toolName, args };
|
|
1403
1168
|
return { toolName: "write", args: { filePath, content } };
|
|
1404
1169
|
}
|
|
1405
1170
|
case "edit_file": {
|
|
1406
|
-
const filePath =
|
|
1407
|
-
const oldString =
|
|
1408
|
-
const newString =
|
|
1171
|
+
const filePath = asString2(args.file_path);
|
|
1172
|
+
const oldString = asString2(args.old_str);
|
|
1173
|
+
const newString = asString2(args.new_str);
|
|
1409
1174
|
if (!filePath || oldString === void 0 || newString === void 0) return { toolName, args };
|
|
1410
1175
|
return { toolName: "edit", args: { filePath, oldString, newString } };
|
|
1411
1176
|
}
|
|
1412
1177
|
case "find_files": {
|
|
1413
|
-
const pattern =
|
|
1178
|
+
const pattern = asString2(args.name_pattern);
|
|
1414
1179
|
if (!pattern) return { toolName, args };
|
|
1415
1180
|
return { toolName: "glob", args: { pattern } };
|
|
1416
1181
|
}
|
|
1417
1182
|
case "grep": {
|
|
1418
|
-
const pattern =
|
|
1183
|
+
const pattern = asString2(args.pattern);
|
|
1419
1184
|
if (!pattern) return { toolName, args };
|
|
1420
|
-
const searchDir =
|
|
1185
|
+
const searchDir = asString2(args.search_directory);
|
|
1421
1186
|
const caseInsensitive = Boolean(args.case_insensitive);
|
|
1422
1187
|
const normalizedPattern = caseInsensitive && !pattern.startsWith("(?i)") ? `(?i)${pattern}` : pattern;
|
|
1423
1188
|
const mapped = { pattern: normalizedPattern };
|
|
@@ -1425,43 +1190,67 @@ function mapDuoToolRequest(toolName, args) {
|
|
|
1425
1190
|
return { toolName: "grep", args: mapped };
|
|
1426
1191
|
}
|
|
1427
1192
|
case "mkdir": {
|
|
1428
|
-
const directory =
|
|
1193
|
+
const directory = asString2(args.directory_path);
|
|
1429
1194
|
if (!directory) return { toolName, args };
|
|
1430
1195
|
return { toolName: "bash", args: { command: `mkdir -p ${shellQuote(directory)}`, description: "Create directory", workdir: "." } };
|
|
1431
1196
|
}
|
|
1432
1197
|
case "shell_command": {
|
|
1433
|
-
const command =
|
|
1198
|
+
const command = asString2(args.command);
|
|
1434
1199
|
if (!command) return { toolName, args };
|
|
1435
1200
|
return { toolName: "bash", args: { command, description: "Run shell command", workdir: "." } };
|
|
1436
1201
|
}
|
|
1437
1202
|
case "run_command": {
|
|
1438
|
-
const program =
|
|
1203
|
+
const program = asString2(args.program);
|
|
1439
1204
|
if (program) {
|
|
1440
1205
|
const parts = [shellQuote(program)];
|
|
1441
1206
|
if (Array.isArray(args.flags)) parts.push(...args.flags.map((f) => shellQuote(String(f))));
|
|
1442
1207
|
if (Array.isArray(args.arguments)) parts.push(...args.arguments.map((a) => shellQuote(String(a))));
|
|
1443
1208
|
return { toolName: "bash", args: { command: parts.join(" "), description: "Run command", workdir: "." } };
|
|
1444
1209
|
}
|
|
1445
|
-
const command =
|
|
1210
|
+
const command = asString2(args.command);
|
|
1446
1211
|
if (!command) return { toolName, args };
|
|
1447
1212
|
return { toolName: "bash", args: { command, description: "Run command", workdir: "." } };
|
|
1448
1213
|
}
|
|
1449
1214
|
case "run_git_command": {
|
|
1450
|
-
const command =
|
|
1215
|
+
const command = asString2(args.command);
|
|
1451
1216
|
if (!command) return { toolName, args };
|
|
1452
1217
|
const rawArgs = args.args;
|
|
1453
|
-
const extraArgs = Array.isArray(rawArgs) ? rawArgs.map((v) => shellQuote(String(v))).join(" ") :
|
|
1218
|
+
const extraArgs = Array.isArray(rawArgs) ? rawArgs.map((v) => shellQuote(String(v))).join(" ") : asString2(rawArgs);
|
|
1454
1219
|
const gitCmd = extraArgs ? `git ${shellQuote(command)} ${extraArgs}` : `git ${shellQuote(command)}`;
|
|
1455
1220
|
return { toolName: "bash", args: { command: gitCmd, description: "Run git command", workdir: "." } };
|
|
1456
1221
|
}
|
|
1222
|
+
case "gitlab_api_request": {
|
|
1223
|
+
const method = asString2(args.method) ?? "GET";
|
|
1224
|
+
const apiPath = asString2(args.path);
|
|
1225
|
+
if (!apiPath) return { toolName, args };
|
|
1226
|
+
const body = asString2(args.body);
|
|
1227
|
+
const curlParts = [
|
|
1228
|
+
"curl",
|
|
1229
|
+
"-s",
|
|
1230
|
+
"-X",
|
|
1231
|
+
method,
|
|
1232
|
+
"-H",
|
|
1233
|
+
"'Authorization: Bearer $GITLAB_TOKEN'",
|
|
1234
|
+
"-H",
|
|
1235
|
+
"'Content-Type: application/json'"
|
|
1236
|
+
];
|
|
1237
|
+
if (body) {
|
|
1238
|
+
curlParts.push("-d", shellQuote(body));
|
|
1239
|
+
}
|
|
1240
|
+
curlParts.push(shellQuote(`$GITLAB_INSTANCE_URL/api/v4/${apiPath}`));
|
|
1241
|
+
return {
|
|
1242
|
+
toolName: "bash",
|
|
1243
|
+
args: { command: curlParts.join(" "), description: `GitLab API: ${method} ${apiPath}`, workdir: "." }
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1457
1246
|
default:
|
|
1458
1247
|
return { toolName, args };
|
|
1459
1248
|
}
|
|
1460
1249
|
}
|
|
1461
|
-
function
|
|
1250
|
+
function asString2(value) {
|
|
1462
1251
|
return typeof value === "string" ? value : void 0;
|
|
1463
1252
|
}
|
|
1464
|
-
function
|
|
1253
|
+
function asStringArray(value) {
|
|
1465
1254
|
if (!Array.isArray(value)) return [];
|
|
1466
1255
|
return value.filter((v) => typeof v === "string");
|
|
1467
1256
|
}
|
|
@@ -1626,6 +1415,7 @@ var DuoWorkflowModel = class {
|
|
|
1626
1415
|
const toolResults = extractToolResults(options.prompt);
|
|
1627
1416
|
const session = this.#resolveSession(sessionID);
|
|
1628
1417
|
const textId = randomUUID3();
|
|
1418
|
+
dlog(`doStream: goal=${goal?.length ?? 0}chars, toolResults=${toolResults.length}, hasStarted=${session.hasStarted}`);
|
|
1629
1419
|
if (sessionID !== this.#stateSessionId) {
|
|
1630
1420
|
this.#pendingToolRequests.clear();
|
|
1631
1421
|
this.#multiCallGroups.clear();
|
|
@@ -1679,9 +1469,11 @@ var DuoWorkflowModel = class {
|
|
|
1679
1469
|
}
|
|
1680
1470
|
const pending = model.#pendingToolRequests.get(result.toolCallId);
|
|
1681
1471
|
if (!pending) {
|
|
1472
|
+
dlog(`phase1: toolCallId=${result.toolCallId} pending=false SKIP`);
|
|
1682
1473
|
model.#sentToolCallIds.add(result.toolCallId);
|
|
1683
1474
|
continue;
|
|
1684
1475
|
}
|
|
1476
|
+
dlog(`phase1: toolCallId=${result.toolCallId} pending=true SEND output=${result.output.length}b`);
|
|
1685
1477
|
session.sendToolResult(result.toolCallId, result.output, result.error);
|
|
1686
1478
|
sentToolResults = true;
|
|
1687
1479
|
model.#sentToolCallIds.add(result.toolCallId);
|
|
@@ -1732,7 +1524,10 @@ var DuoWorkflowModel = class {
|
|
|
1732
1524
|
let hasText = false;
|
|
1733
1525
|
while (true) {
|
|
1734
1526
|
const event = await session.waitForEvent();
|
|
1735
|
-
if (!event)
|
|
1527
|
+
if (!event) {
|
|
1528
|
+
dlog("phase3: event=null (queue closed)");
|
|
1529
|
+
break;
|
|
1530
|
+
}
|
|
1736
1531
|
if (event.type === "text-delta") {
|
|
1737
1532
|
if (!event.value) continue;
|
|
1738
1533
|
if (!hasText) {
|
|
@@ -1746,7 +1541,10 @@ var DuoWorkflowModel = class {
|
|
|
1746
1541
|
let mapped;
|
|
1747
1542
|
try {
|
|
1748
1543
|
mapped = mapDuoToolRequest(event.toolName, event.args);
|
|
1544
|
+
const name = Array.isArray(mapped) ? mapped.map((m) => m.toolName).join(",") : mapped.toolName;
|
|
1545
|
+
dlog(`phase3: tool-request ${event.toolName} \u2192 ${name} reqId=${event.requestId}`);
|
|
1749
1546
|
} catch {
|
|
1547
|
+
dlog(`phase3: tool-request ${event.toolName} MAPPING FAILED`);
|
|
1750
1548
|
continue;
|
|
1751
1549
|
}
|
|
1752
1550
|
if (hasText) {
|