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.
Files changed (2) hide show
  1. package/dist/index.js +199 -401
  2. 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 stat2 = await fs.stat(gitPath);
139
- if (stat2.isDirectory()) {
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((resolve2) => this.#waiters.push(resolve2));
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((resolve2, reject) => {
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
- resolve2();
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/tool-executor.ts
654
- import { readFile, readdir, writeFile, mkdir as fsMkdir, stat } from "fs/promises";
655
- import { exec } from "child_process";
656
- import { resolve, dirname } from "path";
657
- function asString(value) {
658
- return typeof value === "string" ? value : void 0;
659
- }
660
- function asNumber(value) {
661
- return typeof value === "number" ? value : void 0;
662
- }
663
- function asStringArray(value) {
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
- } catch (err) {
765
- console.error(`[tool-executor] executeDuoTool error:`, err);
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
- * Route a Duo WorkflowToolAction to the appropriate local handler.
771
- * This handles standalone WebSocket actions (non-checkpoint path).
772
- */
773
- async executeAction(action) {
774
- try {
775
- if (action.runReadFile) {
776
- return this.#read({
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
- const keys = Object.keys(action).filter((k) => k !== "requestID");
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
- // ── Handlers ──────────────────────────────────────────────────────
865
- async #bash(args) {
866
- const cwd = args.workdir ? resolve(this.#cwd, args.workdir) : this.#cwd;
867
- const timeout = args.timeout ?? 12e4;
868
- return new Promise((res) => {
869
- exec(args.command, { cwd, timeout, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
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
- async #read(args) {
880
- try {
881
- const filepath = resolve(this.#cwd, args.filePath);
882
- const info = await stat(filepath);
883
- if (info.isDirectory()) {
884
- const entries = await readdir(filepath, { withFileTypes: true });
885
- const lines = entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name);
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
- const content = await readFile(filepath, "utf-8");
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
- async #readMultiple(filepaths) {
900
- const results = [];
901
- for (const fp of filepaths) {
902
- const result = await this.#read({ filePath: fp });
903
- if (result.error) {
904
- results.push(`--- ${fp} ---
905
- ERROR: ${result.error}`);
906
- } else {
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
- async #edit(args) {
914
- try {
915
- const filepath = resolve(this.#cwd, args.filePath);
916
- let content = await readFile(filepath, "utf-8");
917
- if (args.replaceAll) {
918
- if (!content.includes(args.oldString)) {
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
- async #write(args) {
940
- try {
941
- const filepath = resolve(this.#cwd, args.filePath);
942
- await fsMkdir(dirname(filepath), { recursive: true });
943
- await writeFile(filepath, args.content, "utf-8");
944
- return { response: `Successfully wrote ${args.filePath}` };
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
- async #glob(args) {
950
- const cwd = args.path ? resolve(this.#cwd, args.path) : this.#cwd;
951
- return new Promise((res) => {
952
- const command = process.platform === "win32" ? `dir /s /b "${args.pattern}"` : `find . -path "./${args.pattern}" -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null | head -200`;
953
- exec(command, { cwd, timeout: 3e4 }, (error, stdout) => {
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
- async #grep(args) {
966
- const cwd = args.path ? resolve(this.#cwd, args.path) : this.#cwd;
967
- const caseFlag = args.caseInsensitive ? "--ignore-case" : "";
968
- return new Promise((res) => {
969
- const includeFlag = args.include ? `--glob '${args.include}'` : "";
970
- const escapedPattern = args.pattern.replace(/'/g, "'\\''");
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
- async #mkdir(directoryPath) {
987
- try {
988
- const filepath = resolve(this.#cwd, directoryPath);
989
- await fsMkdir(filepath, { recursive: true });
990
- return { response: `Created directory ${directoryPath}` };
991
- } catch (err) {
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
- async #httpRequest(args) {
996
- if (!this.#client) {
997
- return { response: "", error: "HTTP requests require GitLab client credentials" };
998
- }
999
- try {
1000
- const url = `${this.#client.instanceUrl}/api/v4/${args.path}`;
1001
- const headers = {
1002
- authorization: `Bearer ${this.#client.token}`
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
- const response = await fetch(url, {
1008
- method: args.method,
1009
- headers,
1010
- body: args.body ?? void 0
1011
- });
1012
- const text2 = await response.text();
1013
- if (!response.ok) {
1014
- return { response: text2, error: `HTTP ${response.status}: ${response.statusText}` };
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
- return { response: text2 };
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) throw new Error("Not connected");
1122
- console.error(`[duo-workflow] sendToolResult: requestId=${requestId} output=${output.length} bytes, error=${error ?? "none"}`);
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) return null;
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
- if (action.requestID) {
1175
- console.error(`[duo-workflow] ws tool action: keys=${JSON.stringify(Object.keys(action).filter((k) => k !== "requestID"))}`);
1176
- this.#executeStandaloneAction(action);
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 ?? asString2(p.error) ?? asString2(p.errorText);
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 = asString2(errorValue) ?? String(errorValue ?? "");
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 asString2(value) {
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) ? asString2(resultField.error) : void 0;
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 = asString3(args.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 = asString3(args.file_path) ?? asString3(args.filepath) ?? asString3(args.filePath) ?? asString3(args.path);
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 = asStringArray2(args.file_paths);
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 = asString3(args.file_path);
1401
- const content = asString3(args.contents);
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 = asString3(args.file_path);
1407
- const oldString = asString3(args.old_str);
1408
- const newString = asString3(args.new_str);
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 = asString3(args.name_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 = asString3(args.pattern);
1183
+ const pattern = asString2(args.pattern);
1419
1184
  if (!pattern) return { toolName, args };
1420
- const searchDir = asString3(args.search_directory);
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 = asString3(args.directory_path);
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 = asString3(args.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 = asString3(args.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 = asString3(args.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 = asString3(args.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(" ") : asString3(rawArgs);
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 asString3(value) {
1250
+ function asString2(value) {
1462
1251
  return typeof value === "string" ? value : void 0;
1463
1252
  }
1464
- function asStringArray2(value) {
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) break;
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-gitlab-duo-agentic",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "OpenCode plugin and provider for GitLab Duo Agentic workflows",
5
5
  "license": "MIT",
6
6
  "type": "module",