opencode-gitlab-duo-agentic 0.2.5 → 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 +379 -386
- 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.
|
|
@@ -1089,7 +843,7 @@ var WorkflowSession = class {
|
|
|
1089
843
|
// ---------------------------------------------------------------------------
|
|
1090
844
|
// Messaging
|
|
1091
845
|
// ---------------------------------------------------------------------------
|
|
1092
|
-
sendStartRequest(goal) {
|
|
846
|
+
sendStartRequest(goal, additionalContext = []) {
|
|
1093
847
|
if (!this.#socket || !this.#workflowId) throw new Error("Not connected");
|
|
1094
848
|
const mcpTools = this.#toolsConfig?.mcpTools ?? [];
|
|
1095
849
|
const preapprovedTools = mcpTools.map((t) => t.name);
|
|
@@ -1104,7 +858,7 @@ var WorkflowSession = class {
|
|
|
1104
858
|
}),
|
|
1105
859
|
clientCapabilities: ["shell_command"],
|
|
1106
860
|
mcpTools,
|
|
1107
|
-
additional_context:
|
|
861
|
+
additional_context: additionalContext,
|
|
1108
862
|
preapproved_tools: preapprovedTools,
|
|
1109
863
|
...this.#toolsConfig?.flowConfig ? {
|
|
1110
864
|
flowConfig: this.#toolsConfig.flowConfig,
|
|
@@ -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
|
// ---------------------------------------------------------------------------
|
|
@@ -1265,16 +1013,28 @@ function extractToolResults(prompt) {
|
|
|
1265
1013
|
if (!Array.isArray(content)) continue;
|
|
1266
1014
|
for (const part of content) {
|
|
1267
1015
|
const p = part;
|
|
1268
|
-
if (p.type
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1016
|
+
if (p.type === "tool-result") {
|
|
1017
|
+
const toolCallId = String(p.toolCallId ?? "");
|
|
1018
|
+
const toolName = String(p.toolName ?? "");
|
|
1019
|
+
if (!toolCallId) continue;
|
|
1020
|
+
const { output, error } = parseToolResultOutput(p);
|
|
1021
|
+
const finalError = error ?? asString(p.error) ?? asString(p.errorText);
|
|
1022
|
+
results.push({ toolCallId, toolName, output, error: finalError });
|
|
1023
|
+
}
|
|
1024
|
+
if (p.type === "tool-error") {
|
|
1025
|
+
const toolCallId = String(p.toolCallId ?? "");
|
|
1026
|
+
const toolName = String(p.toolName ?? "");
|
|
1027
|
+
const errorValue = p.error ?? p.errorText ?? p.message;
|
|
1028
|
+
const error = asString(errorValue) ?? String(errorValue ?? "");
|
|
1029
|
+
results.push({ toolCallId, toolName, output: "", error });
|
|
1030
|
+
}
|
|
1274
1031
|
}
|
|
1275
1032
|
}
|
|
1276
1033
|
return results;
|
|
1277
1034
|
}
|
|
1035
|
+
function asString(value) {
|
|
1036
|
+
return typeof value === "string" ? value : void 0;
|
|
1037
|
+
}
|
|
1278
1038
|
function parseToolResultOutput(part) {
|
|
1279
1039
|
const outputField = part.output;
|
|
1280
1040
|
const resultField = part.result;
|
|
@@ -1296,10 +1056,67 @@ function parseToolResultOutput(part) {
|
|
|
1296
1056
|
return { output: typeof outputField === "string" ? outputField : JSON.stringify(outputField) };
|
|
1297
1057
|
}
|
|
1298
1058
|
if (resultField !== void 0) {
|
|
1299
|
-
|
|
1059
|
+
const output = typeof resultField === "string" ? resultField : JSON.stringify(resultField);
|
|
1060
|
+
const error = isPlainObject(resultField) ? asString(resultField.error) : void 0;
|
|
1061
|
+
return { output, error };
|
|
1300
1062
|
}
|
|
1301
1063
|
return { output: "" };
|
|
1302
1064
|
}
|
|
1065
|
+
function extractSystemPrompt(prompt) {
|
|
1066
|
+
if (!Array.isArray(prompt)) return null;
|
|
1067
|
+
const parts = [];
|
|
1068
|
+
for (const message of prompt) {
|
|
1069
|
+
const msg = message;
|
|
1070
|
+
if (msg.role === "system" && typeof msg.content === "string" && msg.content.trim()) {
|
|
1071
|
+
parts.push(msg.content);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
return parts.length > 0 ? parts.join("\n") : null;
|
|
1075
|
+
}
|
|
1076
|
+
function sanitizeSystemPrompt(prompt) {
|
|
1077
|
+
let result = prompt;
|
|
1078
|
+
result = result.replace(/^You are [Oo]pen[Cc]ode[,.].*$/gm, "");
|
|
1079
|
+
result = result.replace(/^Your name is opencode\s*$/gm, "");
|
|
1080
|
+
result = result.replace(
|
|
1081
|
+
/If the user asks for help or wants to give feedback[\s\S]*?https:\/\/github\.com\/anomalyco\/opencode\s*/g,
|
|
1082
|
+
""
|
|
1083
|
+
);
|
|
1084
|
+
result = result.replace(
|
|
1085
|
+
/When the user directly asks about OpenCode[\s\S]*?https:\/\/opencode\.ai\/docs\s*/g,
|
|
1086
|
+
""
|
|
1087
|
+
);
|
|
1088
|
+
result = result.replace(/https:\/\/github\.com\/anomalyco\/opencode\S*/g, "");
|
|
1089
|
+
result = result.replace(/https:\/\/opencode\.ai\S*/g, "");
|
|
1090
|
+
result = result.replace(/\bOpenCode\b/g, "GitLab Duo");
|
|
1091
|
+
result = result.replace(/\bopencode\b/g, "GitLab Duo");
|
|
1092
|
+
result = result.replace(/The exact model ID is GitLab Duo\//g, "The exact model ID is ");
|
|
1093
|
+
result = result.replace(/\n{3,}/g, "\n\n");
|
|
1094
|
+
return result.trim();
|
|
1095
|
+
}
|
|
1096
|
+
function extractAgentReminders(prompt) {
|
|
1097
|
+
if (!Array.isArray(prompt)) return [];
|
|
1098
|
+
let textParts = [];
|
|
1099
|
+
for (let i = prompt.length - 1; i >= 0; i--) {
|
|
1100
|
+
const message = prompt[i];
|
|
1101
|
+
if (message?.role !== "user" || !Array.isArray(message.content)) continue;
|
|
1102
|
+
textParts = message.content.filter((p) => p.type === "text");
|
|
1103
|
+
if (textParts.length > 0) break;
|
|
1104
|
+
}
|
|
1105
|
+
if (textParts.length === 0) return [];
|
|
1106
|
+
const reminders = [];
|
|
1107
|
+
for (const part of textParts) {
|
|
1108
|
+
if (!part.text) continue;
|
|
1109
|
+
const text2 = String(part.text);
|
|
1110
|
+
if (part.synthetic) {
|
|
1111
|
+
const trimmed = text2.trim();
|
|
1112
|
+
if (trimmed.length > 0) reminders.push(trimmed);
|
|
1113
|
+
continue;
|
|
1114
|
+
}
|
|
1115
|
+
const matches = text2.match(/<system-reminder>[\s\S]*?<\/system-reminder>/g);
|
|
1116
|
+
if (matches) reminders.push(...matches);
|
|
1117
|
+
}
|
|
1118
|
+
return reminders;
|
|
1119
|
+
}
|
|
1303
1120
|
function isPlainObject(value) {
|
|
1304
1121
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1305
1122
|
}
|
|
@@ -1323,7 +1140,7 @@ function mapDuoToolRequest(toolName, args) {
|
|
|
1323
1140
|
return { toolName: "read", args: mapped };
|
|
1324
1141
|
}
|
|
1325
1142
|
case "read_files": {
|
|
1326
|
-
const filePaths =
|
|
1143
|
+
const filePaths = asStringArray(args.file_paths);
|
|
1327
1144
|
if (filePaths.length === 0) return { toolName, args };
|
|
1328
1145
|
return filePaths.map((fp) => ({ toolName: "read", args: { filePath: fp } }));
|
|
1329
1146
|
}
|
|
@@ -1385,6 +1202,30 @@ function mapDuoToolRequest(toolName, args) {
|
|
|
1385
1202
|
const gitCmd = extraArgs ? `git ${shellQuote(command)} ${extraArgs}` : `git ${shellQuote(command)}`;
|
|
1386
1203
|
return { toolName: "bash", args: { command: gitCmd, description: "Run git command", workdir: "." } };
|
|
1387
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
|
+
}
|
|
1388
1229
|
default:
|
|
1389
1230
|
return { toolName, args };
|
|
1390
1231
|
}
|
|
@@ -1392,7 +1233,7 @@ function mapDuoToolRequest(toolName, args) {
|
|
|
1392
1233
|
function asString2(value) {
|
|
1393
1234
|
return typeof value === "string" ? value : void 0;
|
|
1394
1235
|
}
|
|
1395
|
-
function
|
|
1236
|
+
function asStringArray(value) {
|
|
1396
1237
|
if (!Array.isArray(value)) return [];
|
|
1397
1238
|
return value.filter((v) => typeof v === "string");
|
|
1398
1239
|
}
|
|
@@ -1421,6 +1262,85 @@ function readProviderBlock(options) {
|
|
|
1421
1262
|
return void 0;
|
|
1422
1263
|
}
|
|
1423
1264
|
|
|
1265
|
+
// src/provider/system-context.ts
|
|
1266
|
+
import os2 from "os";
|
|
1267
|
+
function buildSystemContext() {
|
|
1268
|
+
const platform = os2.platform();
|
|
1269
|
+
const arch = os2.arch();
|
|
1270
|
+
return [
|
|
1271
|
+
{
|
|
1272
|
+
category: "os_information",
|
|
1273
|
+
content: `<os><platform>${platform}</platform><architecture>${arch}</architecture></os>`,
|
|
1274
|
+
id: "os_information",
|
|
1275
|
+
metadata: JSON.stringify({
|
|
1276
|
+
title: "Operating System",
|
|
1277
|
+
enabled: true,
|
|
1278
|
+
subType: "os"
|
|
1279
|
+
})
|
|
1280
|
+
},
|
|
1281
|
+
{
|
|
1282
|
+
category: "user_rule",
|
|
1283
|
+
content: SYSTEM_RULES,
|
|
1284
|
+
id: "user_rules",
|
|
1285
|
+
metadata: JSON.stringify({
|
|
1286
|
+
title: "System Rules",
|
|
1287
|
+
enabled: true,
|
|
1288
|
+
subType: "user_rule"
|
|
1289
|
+
})
|
|
1290
|
+
}
|
|
1291
|
+
];
|
|
1292
|
+
}
|
|
1293
|
+
var SYSTEM_RULES = `<system-reminder>
|
|
1294
|
+
You MUST follow ALL the rules in this block strictly.
|
|
1295
|
+
|
|
1296
|
+
<tool_orchestration>
|
|
1297
|
+
PARALLEL EXECUTION:
|
|
1298
|
+
- When gathering information, plan all needed searches upfront and execute
|
|
1299
|
+
them together using multiple tool calls in the same turn where possible.
|
|
1300
|
+
- Read multiple related files together rather than one at a time.
|
|
1301
|
+
- Patterns: grep + find_files together, read_file for multiple files together.
|
|
1302
|
+
|
|
1303
|
+
SEQUENTIAL EXECUTION (only when output depends on previous step):
|
|
1304
|
+
- Read a file BEFORE editing it (always).
|
|
1305
|
+
- Check dependencies BEFORE importing them.
|
|
1306
|
+
- Run tests AFTER making changes.
|
|
1307
|
+
|
|
1308
|
+
READ BEFORE WRITE:
|
|
1309
|
+
- Always read existing files before modifying them to understand context.
|
|
1310
|
+
- Check for existing patterns (naming, imports, error handling) and match them.
|
|
1311
|
+
- Verify the exact content to replace when using edit_file.
|
|
1312
|
+
|
|
1313
|
+
ERROR HANDLING:
|
|
1314
|
+
- If a tool fails, analyze the error before retrying.
|
|
1315
|
+
- If a shell command fails, check the error output and adapt.
|
|
1316
|
+
- Do not repeat the same failing operation without changes.
|
|
1317
|
+
</tool_orchestration>
|
|
1318
|
+
|
|
1319
|
+
<development_workflow>
|
|
1320
|
+
For software development tasks, follow this workflow:
|
|
1321
|
+
|
|
1322
|
+
1. UNDERSTAND: Read relevant files, explore the codebase structure
|
|
1323
|
+
2. PLAN: Break down the task into clear steps
|
|
1324
|
+
3. IMPLEMENT: Make changes methodically, one step at a time
|
|
1325
|
+
4. VERIFY: Run tests, type-checking, or build to validate changes
|
|
1326
|
+
5. COMPLETE: Summarize what was accomplished
|
|
1327
|
+
|
|
1328
|
+
CODE QUALITY:
|
|
1329
|
+
- Match existing code style and patterns in the project
|
|
1330
|
+
- Write immediately executable code (no TODOs or placeholders)
|
|
1331
|
+
- Prefer editing existing files over creating new ones
|
|
1332
|
+
- Use the project's established error handling patterns
|
|
1333
|
+
</development_workflow>
|
|
1334
|
+
|
|
1335
|
+
<communication>
|
|
1336
|
+
- Be concise and direct. Responses appear in a chat panel.
|
|
1337
|
+
- Focus on practical solutions over theoretical discussion.
|
|
1338
|
+
- When unable to complete a request, explain the limitation briefly and
|
|
1339
|
+
provide alternatives.
|
|
1340
|
+
- Use active language: "Analyzing...", "Searching..." instead of "Let me..."
|
|
1341
|
+
</communication>
|
|
1342
|
+
</system-reminder>`;
|
|
1343
|
+
|
|
1424
1344
|
// src/provider/duo-workflow-model.ts
|
|
1425
1345
|
var sessions = /* @__PURE__ */ new Map();
|
|
1426
1346
|
var UNKNOWN_USAGE = {
|
|
@@ -1442,6 +1362,8 @@ var DuoWorkflowModel = class {
|
|
|
1442
1362
|
#sentToolCallIds = /* @__PURE__ */ new Set();
|
|
1443
1363
|
#lastSentGoal = null;
|
|
1444
1364
|
#stateSessionId;
|
|
1365
|
+
#agentMode;
|
|
1366
|
+
#agentModeReminder;
|
|
1445
1367
|
constructor(modelId, client, cwd) {
|
|
1446
1368
|
this.modelId = modelId;
|
|
1447
1369
|
this.#client = client;
|
|
@@ -1481,6 +1403,8 @@ var DuoWorkflowModel = class {
|
|
|
1481
1403
|
this.#multiCallGroups.clear();
|
|
1482
1404
|
this.#sentToolCallIds.clear();
|
|
1483
1405
|
this.#lastSentGoal = null;
|
|
1406
|
+
this.#agentMode = void 0;
|
|
1407
|
+
this.#agentModeReminder = void 0;
|
|
1484
1408
|
this.#stateSessionId = sessionID;
|
|
1485
1409
|
}
|
|
1486
1410
|
const model = this;
|
|
@@ -1491,6 +1415,15 @@ var DuoWorkflowModel = class {
|
|
|
1491
1415
|
const onAbort = () => session.abort();
|
|
1492
1416
|
options.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
1493
1417
|
try {
|
|
1418
|
+
if (!session.hasStarted) {
|
|
1419
|
+
model.#sentToolCallIds.clear();
|
|
1420
|
+
for (const r of toolResults) {
|
|
1421
|
+
if (!model.#pendingToolRequests.has(r.toolCallId)) {
|
|
1422
|
+
model.#sentToolCallIds.add(r.toolCallId);
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
model.#lastSentGoal = null;
|
|
1426
|
+
}
|
|
1494
1427
|
const freshResults = toolResults.filter(
|
|
1495
1428
|
(r) => !model.#sentToolCallIds.has(r.toolCallId)
|
|
1496
1429
|
);
|
|
@@ -1530,7 +1463,41 @@ var DuoWorkflowModel = class {
|
|
|
1530
1463
|
if (!sentToolResults && isNewGoal) {
|
|
1531
1464
|
await session.ensureConnected(goal);
|
|
1532
1465
|
if (!session.hasStarted) {
|
|
1533
|
-
|
|
1466
|
+
const extraContext = [];
|
|
1467
|
+
extraContext.push(...buildSystemContext());
|
|
1468
|
+
const systemPrompt = extractSystemPrompt(options.prompt);
|
|
1469
|
+
if (systemPrompt) {
|
|
1470
|
+
extraContext.push({
|
|
1471
|
+
category: "agent_context",
|
|
1472
|
+
content: sanitizeSystemPrompt(systemPrompt),
|
|
1473
|
+
id: "agent_system_prompt",
|
|
1474
|
+
metadata: JSON.stringify({
|
|
1475
|
+
title: "Agent System Prompt",
|
|
1476
|
+
enabled: true,
|
|
1477
|
+
subType: "system_prompt"
|
|
1478
|
+
})
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1481
|
+
const agentReminders = extractAgentReminders(options.prompt);
|
|
1482
|
+
const modeReminder = detectLatestModeReminder(agentReminders);
|
|
1483
|
+
if (modeReminder) {
|
|
1484
|
+
model.#agentMode = modeReminder.mode;
|
|
1485
|
+
model.#agentModeReminder = modeReminder.reminder;
|
|
1486
|
+
}
|
|
1487
|
+
const remindersForContext = buildReminderContext(agentReminders, model.#agentModeReminder);
|
|
1488
|
+
if (remindersForContext.length > 0) {
|
|
1489
|
+
extraContext.push({
|
|
1490
|
+
category: "agent_context",
|
|
1491
|
+
content: sanitizeSystemPrompt(remindersForContext.join("\n\n")),
|
|
1492
|
+
id: "agent_reminders",
|
|
1493
|
+
metadata: JSON.stringify({
|
|
1494
|
+
title: "Agent Reminders",
|
|
1495
|
+
enabled: true,
|
|
1496
|
+
subType: "agent_reminders"
|
|
1497
|
+
})
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
session.sendStartRequest(goal, extraContext);
|
|
1534
1501
|
}
|
|
1535
1502
|
model.#lastSentGoal = goal;
|
|
1536
1503
|
}
|
|
@@ -1638,6 +1605,32 @@ var DuoWorkflowModel = class {
|
|
|
1638
1605
|
function sessionKey(instanceUrl, modelId, sessionID) {
|
|
1639
1606
|
return `${instanceUrl}::${modelId}::${sessionID}`;
|
|
1640
1607
|
}
|
|
1608
|
+
function detectLatestModeReminder(reminders) {
|
|
1609
|
+
let latest;
|
|
1610
|
+
for (const reminder of reminders) {
|
|
1611
|
+
const classification = classifyModeReminder(reminder);
|
|
1612
|
+
if (classification === "other") continue;
|
|
1613
|
+
latest = { mode: classification, reminder };
|
|
1614
|
+
}
|
|
1615
|
+
return latest;
|
|
1616
|
+
}
|
|
1617
|
+
function buildReminderContext(reminders, modeReminder) {
|
|
1618
|
+
const nonModeReminders = reminders.filter(
|
|
1619
|
+
(r) => classifyModeReminder(r) === "other"
|
|
1620
|
+
);
|
|
1621
|
+
if (!modeReminder) return nonModeReminders;
|
|
1622
|
+
return [...nonModeReminders, modeReminder];
|
|
1623
|
+
}
|
|
1624
|
+
function classifyModeReminder(reminder) {
|
|
1625
|
+
const text2 = reminder.toLowerCase();
|
|
1626
|
+
if (text2.includes("operational mode has changed from build to plan")) return "plan";
|
|
1627
|
+
if (text2.includes("operational mode has changed from plan to build")) return "build";
|
|
1628
|
+
if (text2.includes("you are no longer in read-only mode")) return "build";
|
|
1629
|
+
if (text2.includes("you are now in read-only mode")) return "plan";
|
|
1630
|
+
if (text2.includes("you are in read-only mode")) return "plan";
|
|
1631
|
+
if (text2.includes("you are permitted to make file changes")) return "build";
|
|
1632
|
+
return "other";
|
|
1633
|
+
}
|
|
1641
1634
|
|
|
1642
1635
|
// src/provider/index.ts
|
|
1643
1636
|
function createFallbackProvider(input = {}) {
|