oh-my-opencode 0.1.26 → 0.1.28
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/README.ko.md +11 -5
- package/README.md +11 -5
- package/dist/hooks/directory-agents-injector/constants.d.ts +3 -0
- package/dist/hooks/directory-agents-injector/index.d.ts +22 -0
- package/dist/hooks/directory-agents-injector/storage.d.ts +3 -0
- package/dist/hooks/directory-agents-injector/types.d.ts +5 -0
- package/dist/hooks/empty-task-response-detector.d.ts +12 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/pulse-monitor.d.ts +1 -1
- package/dist/hooks/session-recovery/constants.d.ts +6 -0
- package/dist/hooks/session-recovery/index.d.ts +14 -0
- package/dist/hooks/session-recovery/storage.d.ts +14 -0
- package/dist/hooks/session-recovery/types.d.ts +75 -0
- package/dist/index.js +634 -281
- package/dist/tools/glob/cli.d.ts +2 -0
- package/dist/tools/glob/constants.d.ts +6 -0
- package/dist/tools/glob/index.d.ts +2 -0
- package/dist/tools/glob/tools.d.ts +11 -0
- package/dist/tools/glob/types.d.ts +19 -0
- package/dist/tools/glob/utils.d.ts +2 -0
- package/dist/tools/grep/index.d.ts +2 -0
- package/dist/tools/{safe-grep → grep}/tools.d.ts +1 -1
- package/dist/tools/index.d.ts +12 -1
- package/package.json +1 -1
- package/dist/hooks/grep-blocker.d.ts +0 -10
- package/dist/hooks/session-recovery.d.ts +0 -30
- package/dist/tools/safe-grep/index.d.ts +0 -2
- /package/dist/tools/{safe-grep → grep}/cli.d.ts +0 -0
- /package/dist/tools/{safe-grep → grep}/constants.d.ts +0 -0
- /package/dist/tools/{safe-grep → grep}/types.d.ts +0 -0
- /package/dist/tools/{safe-grep → grep}/utils.d.ts +0 -0
package/dist/index.js
CHANGED
|
@@ -784,8 +784,11 @@ ${CONTEXT_REMINDER}
|
|
|
784
784
|
event: eventHandler
|
|
785
785
|
};
|
|
786
786
|
}
|
|
787
|
-
// src/hooks/session-recovery.ts
|
|
788
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs";
|
|
787
|
+
// src/hooks/session-recovery/storage.ts
|
|
788
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
789
|
+
import { join as join2 } from "path";
|
|
790
|
+
|
|
791
|
+
// src/hooks/session-recovery/constants.ts
|
|
789
792
|
import { join } from "path";
|
|
790
793
|
|
|
791
794
|
// node_modules/xdg-basedir/index.js
|
|
@@ -807,141 +810,36 @@ if (xdgConfig) {
|
|
|
807
810
|
xdgConfigDirectories.unshift(xdgConfig);
|
|
808
811
|
}
|
|
809
812
|
|
|
810
|
-
// src/hooks/session-recovery.ts
|
|
813
|
+
// src/hooks/session-recovery/constants.ts
|
|
811
814
|
var OPENCODE_STORAGE = join(xdgData ?? "", "opencode", "storage");
|
|
812
815
|
var MESSAGE_STORAGE = join(OPENCODE_STORAGE, "message");
|
|
813
816
|
var PART_STORAGE = join(OPENCODE_STORAGE, "part");
|
|
814
|
-
function getErrorMessage(error) {
|
|
815
|
-
if (!error)
|
|
816
|
-
return "";
|
|
817
|
-
if (typeof error === "string")
|
|
818
|
-
return error.toLowerCase();
|
|
819
|
-
const errorObj = error;
|
|
820
|
-
return (errorObj.data?.message || errorObj.message || "").toLowerCase();
|
|
821
|
-
}
|
|
822
|
-
function detectErrorType(error) {
|
|
823
|
-
const message = getErrorMessage(error);
|
|
824
|
-
if (message.includes("tool_use") && message.includes("tool_result")) {
|
|
825
|
-
return "tool_result_missing";
|
|
826
|
-
}
|
|
827
|
-
if (message.includes("thinking") && (message.includes("first block") || message.includes("must start with") || message.includes("preceeding"))) {
|
|
828
|
-
return "thinking_block_order";
|
|
829
|
-
}
|
|
830
|
-
if (message.includes("thinking is disabled") && message.includes("cannot contain")) {
|
|
831
|
-
return "thinking_disabled_violation";
|
|
832
|
-
}
|
|
833
|
-
if (message.includes("non-empty content") || message.includes("must have non-empty content")) {
|
|
834
|
-
return "empty_content_message";
|
|
835
|
-
}
|
|
836
|
-
return null;
|
|
837
|
-
}
|
|
838
|
-
function extractToolUseIds(parts) {
|
|
839
|
-
return parts.filter((p) => p.type === "tool_use" && !!p.id).map((p) => p.id);
|
|
840
|
-
}
|
|
841
|
-
async function recoverToolResultMissing(client, sessionID, failedAssistantMsg) {
|
|
842
|
-
const parts = failedAssistantMsg.parts || [];
|
|
843
|
-
const toolUseIds = extractToolUseIds(parts);
|
|
844
|
-
if (toolUseIds.length === 0) {
|
|
845
|
-
return false;
|
|
846
|
-
}
|
|
847
|
-
const toolResultParts = toolUseIds.map((id) => ({
|
|
848
|
-
type: "tool_result",
|
|
849
|
-
tool_use_id: id,
|
|
850
|
-
content: "Operation cancelled by user (ESC pressed)"
|
|
851
|
-
}));
|
|
852
|
-
try {
|
|
853
|
-
await client.session.prompt({
|
|
854
|
-
path: { id: sessionID },
|
|
855
|
-
body: { parts: toolResultParts }
|
|
856
|
-
});
|
|
857
|
-
return true;
|
|
858
|
-
} catch {
|
|
859
|
-
return false;
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
async function recoverThinkingBlockOrder(client, sessionID, failedAssistantMsg, directory) {
|
|
863
|
-
const messageID = failedAssistantMsg.info?.id;
|
|
864
|
-
if (!messageID) {
|
|
865
|
-
return false;
|
|
866
|
-
}
|
|
867
|
-
const existingParts = failedAssistantMsg.parts || [];
|
|
868
|
-
const patchedParts = [{ type: "thinking", thinking: "" }, ...existingParts];
|
|
869
|
-
try {
|
|
870
|
-
await client.message?.update?.({
|
|
871
|
-
path: { id: messageID },
|
|
872
|
-
body: { parts: patchedParts }
|
|
873
|
-
});
|
|
874
|
-
return true;
|
|
875
|
-
} catch {}
|
|
876
|
-
try {
|
|
877
|
-
await client.session.patch?.({
|
|
878
|
-
path: { id: sessionID },
|
|
879
|
-
body: {
|
|
880
|
-
messageID,
|
|
881
|
-
parts: patchedParts
|
|
882
|
-
}
|
|
883
|
-
});
|
|
884
|
-
return true;
|
|
885
|
-
} catch {}
|
|
886
|
-
return await fallbackRevertStrategy(client, sessionID, failedAssistantMsg, directory);
|
|
887
|
-
}
|
|
888
|
-
async function recoverThinkingDisabledViolation(client, sessionID, failedAssistantMsg) {
|
|
889
|
-
const messageID = failedAssistantMsg.info?.id;
|
|
890
|
-
if (!messageID) {
|
|
891
|
-
return false;
|
|
892
|
-
}
|
|
893
|
-
const existingParts = failedAssistantMsg.parts || [];
|
|
894
|
-
const strippedParts = existingParts.filter((p) => p.type !== "thinking" && p.type !== "redacted_thinking");
|
|
895
|
-
if (strippedParts.length === 0) {
|
|
896
|
-
return false;
|
|
897
|
-
}
|
|
898
|
-
try {
|
|
899
|
-
await client.message?.update?.({
|
|
900
|
-
path: { id: messageID },
|
|
901
|
-
body: { parts: strippedParts }
|
|
902
|
-
});
|
|
903
|
-
return true;
|
|
904
|
-
} catch {}
|
|
905
|
-
try {
|
|
906
|
-
await client.session.patch?.({
|
|
907
|
-
path: { id: sessionID },
|
|
908
|
-
body: {
|
|
909
|
-
messageID,
|
|
910
|
-
parts: strippedParts
|
|
911
|
-
}
|
|
912
|
-
});
|
|
913
|
-
return true;
|
|
914
|
-
} catch {}
|
|
915
|
-
return false;
|
|
916
|
-
}
|
|
917
817
|
var THINKING_TYPES = new Set(["thinking", "redacted_thinking", "reasoning"]);
|
|
918
818
|
var META_TYPES = new Set(["step-start", "step-finish"]);
|
|
819
|
+
var CONTENT_TYPES = new Set(["text", "tool", "tool_use", "tool_result"]);
|
|
820
|
+
|
|
821
|
+
// src/hooks/session-recovery/storage.ts
|
|
919
822
|
function generatePartId() {
|
|
920
823
|
const timestamp = Date.now().toString(16);
|
|
921
824
|
const random = Math.random().toString(36).substring(2, 10);
|
|
922
825
|
return `prt_${timestamp}${random}`;
|
|
923
826
|
}
|
|
924
827
|
function getMessageDir(sessionID) {
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
return false;
|
|
931
|
-
}
|
|
932
|
-
});
|
|
933
|
-
if (projectHash) {
|
|
934
|
-
return join(MESSAGE_STORAGE, projectHash, sessionID);
|
|
828
|
+
if (!existsSync(MESSAGE_STORAGE))
|
|
829
|
+
return "";
|
|
830
|
+
const directPath = join2(MESSAGE_STORAGE, sessionID);
|
|
831
|
+
if (existsSync(directPath)) {
|
|
832
|
+
return directPath;
|
|
935
833
|
}
|
|
936
834
|
for (const dir of readdirSync(MESSAGE_STORAGE)) {
|
|
937
|
-
const sessionPath =
|
|
835
|
+
const sessionPath = join2(MESSAGE_STORAGE, dir, sessionID);
|
|
938
836
|
if (existsSync(sessionPath)) {
|
|
939
837
|
return sessionPath;
|
|
940
838
|
}
|
|
941
839
|
}
|
|
942
840
|
return "";
|
|
943
841
|
}
|
|
944
|
-
function
|
|
842
|
+
function readMessages(sessionID) {
|
|
945
843
|
const messageDir = getMessageDir(sessionID);
|
|
946
844
|
if (!messageDir || !existsSync(messageDir))
|
|
947
845
|
return [];
|
|
@@ -950,7 +848,7 @@ function readMessagesFromStorage(sessionID) {
|
|
|
950
848
|
if (!file.endsWith(".json"))
|
|
951
849
|
continue;
|
|
952
850
|
try {
|
|
953
|
-
const content = readFileSync(
|
|
851
|
+
const content = readFileSync(join2(messageDir, file), "utf-8");
|
|
954
852
|
messages.push(JSON.parse(content));
|
|
955
853
|
} catch {
|
|
956
854
|
continue;
|
|
@@ -958,8 +856,8 @@ function readMessagesFromStorage(sessionID) {
|
|
|
958
856
|
}
|
|
959
857
|
return messages.sort((a, b) => a.id.localeCompare(b.id));
|
|
960
858
|
}
|
|
961
|
-
function
|
|
962
|
-
const partDir =
|
|
859
|
+
function readParts(messageID) {
|
|
860
|
+
const partDir = join2(PART_STORAGE, messageID);
|
|
963
861
|
if (!existsSync(partDir))
|
|
964
862
|
return [];
|
|
965
863
|
const parts = [];
|
|
@@ -967,7 +865,7 @@ function readPartsFromStorage(messageID) {
|
|
|
967
865
|
if (!file.endsWith(".json"))
|
|
968
866
|
continue;
|
|
969
867
|
try {
|
|
970
|
-
const content = readFileSync(
|
|
868
|
+
const content = readFileSync(join2(partDir, file), "utf-8");
|
|
971
869
|
parts.push(JSON.parse(content));
|
|
972
870
|
} catch {
|
|
973
871
|
continue;
|
|
@@ -975,8 +873,29 @@ function readPartsFromStorage(messageID) {
|
|
|
975
873
|
}
|
|
976
874
|
return parts;
|
|
977
875
|
}
|
|
978
|
-
function
|
|
979
|
-
|
|
876
|
+
function hasContent(part) {
|
|
877
|
+
if (THINKING_TYPES.has(part.type))
|
|
878
|
+
return false;
|
|
879
|
+
if (META_TYPES.has(part.type))
|
|
880
|
+
return false;
|
|
881
|
+
if (part.type === "text") {
|
|
882
|
+
const textPart = part;
|
|
883
|
+
return !!textPart.text?.trim();
|
|
884
|
+
}
|
|
885
|
+
if (part.type === "tool" || part.type === "tool_use") {
|
|
886
|
+
return true;
|
|
887
|
+
}
|
|
888
|
+
if (part.type === "tool_result") {
|
|
889
|
+
return true;
|
|
890
|
+
}
|
|
891
|
+
return false;
|
|
892
|
+
}
|
|
893
|
+
function messageHasContent(messageID) {
|
|
894
|
+
const parts = readParts(messageID);
|
|
895
|
+
return parts.some(hasContent);
|
|
896
|
+
}
|
|
897
|
+
function injectTextPart(sessionID, messageID, text) {
|
|
898
|
+
const partDir = join2(PART_STORAGE, messageID);
|
|
980
899
|
if (!existsSync(partDir)) {
|
|
981
900
|
mkdirSync(partDir, { recursive: true });
|
|
982
901
|
}
|
|
@@ -986,17 +905,19 @@ function injectTextPartToStorage(sessionID, messageID, text) {
|
|
|
986
905
|
sessionID,
|
|
987
906
|
messageID,
|
|
988
907
|
type: "text",
|
|
989
|
-
text
|
|
908
|
+
text,
|
|
909
|
+
synthetic: true
|
|
990
910
|
};
|
|
991
911
|
try {
|
|
992
|
-
writeFileSync(
|
|
912
|
+
writeFileSync(join2(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
|
|
993
913
|
return true;
|
|
994
914
|
} catch {
|
|
995
915
|
return false;
|
|
996
916
|
}
|
|
997
917
|
}
|
|
998
|
-
function
|
|
999
|
-
const messages =
|
|
918
|
+
function findEmptyMessages(sessionID) {
|
|
919
|
+
const messages = readMessages(sessionID);
|
|
920
|
+
const emptyIds = [];
|
|
1000
921
|
for (let i = 0;i < messages.length; i++) {
|
|
1001
922
|
const msg = messages[i];
|
|
1002
923
|
if (msg.role !== "assistant")
|
|
@@ -1004,72 +925,187 @@ function findEmptyContentMessageFromStorage(sessionID) {
|
|
|
1004
925
|
const isLastMessage = i === messages.length - 1;
|
|
1005
926
|
if (isLastMessage)
|
|
1006
927
|
continue;
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
if (THINKING_TYPES.has(p.type))
|
|
1010
|
-
return false;
|
|
1011
|
-
if (META_TYPES.has(p.type))
|
|
1012
|
-
return false;
|
|
1013
|
-
if (p.type === "text" && p.text?.trim())
|
|
1014
|
-
return true;
|
|
1015
|
-
if (p.type === "tool_use")
|
|
1016
|
-
return true;
|
|
1017
|
-
if (p.type === "tool_result")
|
|
1018
|
-
return true;
|
|
1019
|
-
return false;
|
|
1020
|
-
});
|
|
1021
|
-
if (!hasContent && parts.length > 0) {
|
|
1022
|
-
return msg.id;
|
|
928
|
+
if (!messageHasContent(msg.id)) {
|
|
929
|
+
emptyIds.push(msg.id);
|
|
1023
930
|
}
|
|
1024
931
|
}
|
|
1025
|
-
return
|
|
932
|
+
return emptyIds;
|
|
1026
933
|
}
|
|
1027
|
-
|
|
1028
|
-
const
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
934
|
+
function findMessagesWithThinkingBlocks(sessionID) {
|
|
935
|
+
const messages = readMessages(sessionID);
|
|
936
|
+
const result = [];
|
|
937
|
+
for (let i = 0;i < messages.length; i++) {
|
|
938
|
+
const msg = messages[i];
|
|
939
|
+
if (msg.role !== "assistant")
|
|
940
|
+
continue;
|
|
941
|
+
const isLastMessage = i === messages.length - 1;
|
|
942
|
+
if (isLastMessage)
|
|
943
|
+
continue;
|
|
944
|
+
const parts = readParts(msg.id);
|
|
945
|
+
const hasThinking = parts.some((p) => THINKING_TYPES.has(p.type));
|
|
946
|
+
if (hasThinking) {
|
|
947
|
+
result.push(msg.id);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return result;
|
|
1032
951
|
}
|
|
1033
|
-
|
|
1034
|
-
const
|
|
1035
|
-
const
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
952
|
+
function findMessagesWithOrphanThinking(sessionID) {
|
|
953
|
+
const messages = readMessages(sessionID);
|
|
954
|
+
const result = [];
|
|
955
|
+
for (let i = 0;i < messages.length; i++) {
|
|
956
|
+
const msg = messages[i];
|
|
957
|
+
if (msg.role !== "assistant")
|
|
958
|
+
continue;
|
|
959
|
+
const isLastMessage = i === messages.length - 1;
|
|
960
|
+
if (isLastMessage)
|
|
961
|
+
continue;
|
|
962
|
+
const parts = readParts(msg.id);
|
|
963
|
+
if (parts.length === 0)
|
|
964
|
+
continue;
|
|
965
|
+
const sortedParts = [...parts].sort((a, b) => a.id.localeCompare(b.id));
|
|
966
|
+
const firstPart = sortedParts[0];
|
|
967
|
+
const hasThinking = parts.some((p) => THINKING_TYPES.has(p.type));
|
|
968
|
+
const firstIsThinking = THINKING_TYPES.has(firstPart.type);
|
|
969
|
+
if (hasThinking && !firstIsThinking) {
|
|
970
|
+
result.push(msg.id);
|
|
971
|
+
}
|
|
1042
972
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
973
|
+
return result;
|
|
974
|
+
}
|
|
975
|
+
function prependThinkingPart(sessionID, messageID) {
|
|
976
|
+
const partDir = join2(PART_STORAGE, messageID);
|
|
977
|
+
if (!existsSync(partDir)) {
|
|
978
|
+
mkdirSync(partDir, { recursive: true });
|
|
1046
979
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
980
|
+
const partId = `prt_0000000000_thinking`;
|
|
981
|
+
const part = {
|
|
982
|
+
id: partId,
|
|
983
|
+
sessionID,
|
|
984
|
+
messageID,
|
|
985
|
+
type: "thinking",
|
|
986
|
+
thinking: "",
|
|
987
|
+
synthetic: true
|
|
988
|
+
};
|
|
989
|
+
try {
|
|
990
|
+
writeFileSync(join2(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
|
|
991
|
+
return true;
|
|
992
|
+
} catch {
|
|
993
|
+
return false;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
function stripThinkingParts(messageID) {
|
|
997
|
+
const partDir = join2(PART_STORAGE, messageID);
|
|
998
|
+
if (!existsSync(partDir))
|
|
999
|
+
return false;
|
|
1000
|
+
let anyRemoved = false;
|
|
1001
|
+
for (const file of readdirSync(partDir)) {
|
|
1002
|
+
if (!file.endsWith(".json"))
|
|
1003
|
+
continue;
|
|
1004
|
+
try {
|
|
1005
|
+
const filePath = join2(partDir, file);
|
|
1006
|
+
const content = readFileSync(filePath, "utf-8");
|
|
1007
|
+
const part = JSON.parse(content);
|
|
1008
|
+
if (THINKING_TYPES.has(part.type)) {
|
|
1009
|
+
unlinkSync(filePath);
|
|
1010
|
+
anyRemoved = true;
|
|
1052
1011
|
}
|
|
1012
|
+
} catch {
|
|
1013
|
+
continue;
|
|
1053
1014
|
}
|
|
1054
1015
|
}
|
|
1055
|
-
|
|
1016
|
+
return anyRemoved;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// src/hooks/session-recovery/index.ts
|
|
1020
|
+
function getErrorMessage(error) {
|
|
1021
|
+
if (!error)
|
|
1022
|
+
return "";
|
|
1023
|
+
if (typeof error === "string")
|
|
1024
|
+
return error.toLowerCase();
|
|
1025
|
+
const errorObj = error;
|
|
1026
|
+
return (errorObj.data?.message || errorObj.error?.message || errorObj.message || "").toLowerCase();
|
|
1027
|
+
}
|
|
1028
|
+
function detectErrorType(error) {
|
|
1029
|
+
const message = getErrorMessage(error);
|
|
1030
|
+
if (message.includes("tool_use") && message.includes("tool_result")) {
|
|
1031
|
+
return "tool_result_missing";
|
|
1032
|
+
}
|
|
1033
|
+
if (message.includes("thinking") && (message.includes("first block") || message.includes("must start with") || message.includes("preceeding"))) {
|
|
1034
|
+
return "thinking_block_order";
|
|
1035
|
+
}
|
|
1036
|
+
if (message.includes("thinking is disabled") && message.includes("cannot contain")) {
|
|
1037
|
+
return "thinking_disabled_violation";
|
|
1038
|
+
}
|
|
1039
|
+
if (message.includes("non-empty content") || message.includes("must have non-empty content")) {
|
|
1040
|
+
return "empty_content_message";
|
|
1041
|
+
}
|
|
1042
|
+
return null;
|
|
1043
|
+
}
|
|
1044
|
+
function extractToolUseIds(parts) {
|
|
1045
|
+
return parts.filter((p) => p.type === "tool_use" && !!p.id).map((p) => p.id);
|
|
1046
|
+
}
|
|
1047
|
+
async function recoverToolResultMissing(client, sessionID, failedAssistantMsg) {
|
|
1048
|
+
const parts = failedAssistantMsg.parts || [];
|
|
1049
|
+
const toolUseIds = extractToolUseIds(parts);
|
|
1050
|
+
if (toolUseIds.length === 0) {
|
|
1051
|
+
return false;
|
|
1052
|
+
}
|
|
1053
|
+
const toolResultParts = toolUseIds.map((id) => ({
|
|
1054
|
+
type: "tool_result",
|
|
1055
|
+
tool_use_id: id,
|
|
1056
|
+
content: "Operation cancelled by user (ESC pressed)"
|
|
1057
|
+
}));
|
|
1058
|
+
try {
|
|
1059
|
+
await client.session.prompt({
|
|
1060
|
+
path: { id: sessionID },
|
|
1061
|
+
body: { parts: toolResultParts }
|
|
1062
|
+
});
|
|
1063
|
+
return true;
|
|
1064
|
+
} catch {
|
|
1056
1065
|
return false;
|
|
1057
1066
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
});
|
|
1063
|
-
const textParts = targetUserMsg.parts.filter((p) => p.type === "text" && p.text).map((p) => ({ type: "text", text: p.text ?? "" }));
|
|
1064
|
-
if (textParts.length === 0) {
|
|
1067
|
+
}
|
|
1068
|
+
async function recoverThinkingBlockOrder(_client, sessionID, _failedAssistantMsg, _directory) {
|
|
1069
|
+
const orphanMessages = findMessagesWithOrphanThinking(sessionID);
|
|
1070
|
+
if (orphanMessages.length === 0) {
|
|
1065
1071
|
return false;
|
|
1066
1072
|
}
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
+
let anySuccess = false;
|
|
1074
|
+
for (const messageID of orphanMessages) {
|
|
1075
|
+
if (prependThinkingPart(sessionID, messageID)) {
|
|
1076
|
+
anySuccess = true;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
return anySuccess;
|
|
1080
|
+
}
|
|
1081
|
+
async function recoverThinkingDisabledViolation(_client, sessionID, _failedAssistantMsg) {
|
|
1082
|
+
const messagesWithThinking = findMessagesWithThinkingBlocks(sessionID);
|
|
1083
|
+
if (messagesWithThinking.length === 0) {
|
|
1084
|
+
return false;
|
|
1085
|
+
}
|
|
1086
|
+
let anySuccess = false;
|
|
1087
|
+
for (const messageID of messagesWithThinking) {
|
|
1088
|
+
if (stripThinkingParts(messageID)) {
|
|
1089
|
+
anySuccess = true;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
return anySuccess;
|
|
1093
|
+
}
|
|
1094
|
+
async function recoverEmptyContentMessage(_client, sessionID, failedAssistantMsg, _directory) {
|
|
1095
|
+
const emptyMessageIDs = findEmptyMessages(sessionID);
|
|
1096
|
+
if (emptyMessageIDs.length === 0) {
|
|
1097
|
+
const fallbackID = failedAssistantMsg.info?.id;
|
|
1098
|
+
if (!fallbackID)
|
|
1099
|
+
return false;
|
|
1100
|
+
return injectTextPart(sessionID, fallbackID, "(interrupted)");
|
|
1101
|
+
}
|
|
1102
|
+
let anySuccess = false;
|
|
1103
|
+
for (const messageID of emptyMessageIDs) {
|
|
1104
|
+
if (injectTextPart(sessionID, messageID, "(interrupted)")) {
|
|
1105
|
+
anySuccess = true;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
return anySuccess;
|
|
1073
1109
|
}
|
|
1074
1110
|
function createSessionRecoveryHook(ctx) {
|
|
1075
1111
|
const processingErrors = new Set;
|
|
@@ -1117,14 +1153,12 @@ function createSessionRecoveryHook(ctx) {
|
|
|
1117
1153
|
tool_result_missing: "Injecting cancelled tool results...",
|
|
1118
1154
|
thinking_block_order: "Fixing message structure...",
|
|
1119
1155
|
thinking_disabled_violation: "Stripping thinking blocks...",
|
|
1120
|
-
empty_content_message: "
|
|
1156
|
+
empty_content_message: "Fixing empty message..."
|
|
1121
1157
|
};
|
|
1122
|
-
const toastTitle = toastTitles[errorType];
|
|
1123
|
-
const toastMessage = toastMessages[errorType];
|
|
1124
1158
|
await ctx.client.tui.showToast({
|
|
1125
1159
|
body: {
|
|
1126
|
-
title:
|
|
1127
|
-
message:
|
|
1160
|
+
title: toastTitles[errorType],
|
|
1161
|
+
message: toastMessages[errorType],
|
|
1128
1162
|
variant: "warning",
|
|
1129
1163
|
duration: 3000
|
|
1130
1164
|
}
|
|
@@ -1155,14 +1189,14 @@ function createSessionRecoveryHook(ctx) {
|
|
|
1155
1189
|
// src/hooks/comment-checker/cli.ts
|
|
1156
1190
|
var {spawn: spawn2 } = globalThis.Bun;
|
|
1157
1191
|
import { createRequire as createRequire2 } from "module";
|
|
1158
|
-
import { dirname, join as
|
|
1192
|
+
import { dirname, join as join4 } from "path";
|
|
1159
1193
|
import { existsSync as existsSync3 } from "fs";
|
|
1160
1194
|
import * as fs from "fs";
|
|
1161
1195
|
|
|
1162
1196
|
// src/hooks/comment-checker/downloader.ts
|
|
1163
1197
|
var {spawn } = globalThis.Bun;
|
|
1164
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, chmodSync, unlinkSync, appendFileSync } from "fs";
|
|
1165
|
-
import { join as
|
|
1198
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, chmodSync, unlinkSync as unlinkSync2, appendFileSync } from "fs";
|
|
1199
|
+
import { join as join3 } from "path";
|
|
1166
1200
|
import { homedir } from "os";
|
|
1167
1201
|
import { createRequire } from "module";
|
|
1168
1202
|
var DEBUG = process.env.COMMENT_CHECKER_DEBUG === "1";
|
|
@@ -1184,14 +1218,14 @@ var PLATFORM_MAP = {
|
|
|
1184
1218
|
};
|
|
1185
1219
|
function getCacheDir() {
|
|
1186
1220
|
const xdgCache2 = process.env.XDG_CACHE_HOME;
|
|
1187
|
-
const base = xdgCache2 ||
|
|
1188
|
-
return
|
|
1221
|
+
const base = xdgCache2 || join3(homedir(), ".cache");
|
|
1222
|
+
return join3(base, "oh-my-opencode", "bin");
|
|
1189
1223
|
}
|
|
1190
1224
|
function getBinaryName() {
|
|
1191
1225
|
return process.platform === "win32" ? "comment-checker.exe" : "comment-checker";
|
|
1192
1226
|
}
|
|
1193
1227
|
function getCachedBinaryPath() {
|
|
1194
|
-
const binaryPath =
|
|
1228
|
+
const binaryPath = join3(getCacheDir(), getBinaryName());
|
|
1195
1229
|
return existsSync2(binaryPath) ? binaryPath : null;
|
|
1196
1230
|
}
|
|
1197
1231
|
function getPackageVersion() {
|
|
@@ -1239,7 +1273,7 @@ async function downloadCommentChecker() {
|
|
|
1239
1273
|
}
|
|
1240
1274
|
const cacheDir = getCacheDir();
|
|
1241
1275
|
const binaryName = getBinaryName();
|
|
1242
|
-
const binaryPath =
|
|
1276
|
+
const binaryPath = join3(cacheDir, binaryName);
|
|
1243
1277
|
if (existsSync2(binaryPath)) {
|
|
1244
1278
|
debugLog("Binary already cached at:", binaryPath);
|
|
1245
1279
|
return binaryPath;
|
|
@@ -1258,7 +1292,7 @@ async function downloadCommentChecker() {
|
|
|
1258
1292
|
if (!response.ok) {
|
|
1259
1293
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
1260
1294
|
}
|
|
1261
|
-
const archivePath =
|
|
1295
|
+
const archivePath = join3(cacheDir, assetName);
|
|
1262
1296
|
const arrayBuffer = await response.arrayBuffer();
|
|
1263
1297
|
await Bun.write(archivePath, arrayBuffer);
|
|
1264
1298
|
debugLog(`Downloaded archive to: ${archivePath}`);
|
|
@@ -1268,7 +1302,7 @@ async function downloadCommentChecker() {
|
|
|
1268
1302
|
await extractZip(archivePath, cacheDir);
|
|
1269
1303
|
}
|
|
1270
1304
|
if (existsSync2(archivePath)) {
|
|
1271
|
-
|
|
1305
|
+
unlinkSync2(archivePath);
|
|
1272
1306
|
}
|
|
1273
1307
|
if (process.platform !== "win32" && existsSync2(binaryPath)) {
|
|
1274
1308
|
chmodSync(binaryPath, 493);
|
|
@@ -1323,7 +1357,7 @@ function findCommentCheckerPathSync() {
|
|
|
1323
1357
|
const require2 = createRequire2(import.meta.url);
|
|
1324
1358
|
const cliPkgPath = require2.resolve("@code-yeongyu/comment-checker/package.json");
|
|
1325
1359
|
const cliDir = dirname(cliPkgPath);
|
|
1326
|
-
const binaryPath =
|
|
1360
|
+
const binaryPath = join4(cliDir, "bin", binaryName);
|
|
1327
1361
|
if (existsSync3(binaryPath)) {
|
|
1328
1362
|
debugLog2("found binary in main package:", binaryPath);
|
|
1329
1363
|
return binaryPath;
|
|
@@ -1337,7 +1371,7 @@ function findCommentCheckerPathSync() {
|
|
|
1337
1371
|
const require2 = createRequire2(import.meta.url);
|
|
1338
1372
|
const pkgPath = require2.resolve(`${platformPkg}/package.json`);
|
|
1339
1373
|
const pkgDir = dirname(pkgPath);
|
|
1340
|
-
const binaryPath =
|
|
1374
|
+
const binaryPath = join4(pkgDir, "bin", binaryName);
|
|
1341
1375
|
if (existsSync3(binaryPath)) {
|
|
1342
1376
|
debugLog2("found binary in platform package:", binaryPath);
|
|
1343
1377
|
return binaryPath;
|
|
@@ -1756,9 +1790,167 @@ function createPulseMonitorHook(ctx) {
|
|
|
1756
1790
|
"tool.execute.before": async () => {
|
|
1757
1791
|
stopMonitoring();
|
|
1758
1792
|
},
|
|
1759
|
-
"tool.execute.after": async (
|
|
1760
|
-
|
|
1761
|
-
|
|
1793
|
+
"tool.execute.after": async (_input) => {}
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
// src/hooks/directory-agents-injector/index.ts
|
|
1797
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
|
|
1798
|
+
import { dirname as dirname2, join as join7, resolve } from "path";
|
|
1799
|
+
|
|
1800
|
+
// src/hooks/directory-agents-injector/storage.ts
|
|
1801
|
+
import {
|
|
1802
|
+
existsSync as existsSync5,
|
|
1803
|
+
mkdirSync as mkdirSync3,
|
|
1804
|
+
readFileSync as readFileSync2,
|
|
1805
|
+
writeFileSync as writeFileSync2,
|
|
1806
|
+
unlinkSync as unlinkSync3
|
|
1807
|
+
} from "fs";
|
|
1808
|
+
import { join as join6 } from "path";
|
|
1809
|
+
|
|
1810
|
+
// src/hooks/directory-agents-injector/constants.ts
|
|
1811
|
+
import { join as join5 } from "path";
|
|
1812
|
+
var OPENCODE_STORAGE2 = join5(xdgData ?? "", "opencode", "storage");
|
|
1813
|
+
var AGENTS_INJECTOR_STORAGE = join5(OPENCODE_STORAGE2, "directory-agents");
|
|
1814
|
+
var AGENTS_FILENAME = "AGENTS.md";
|
|
1815
|
+
|
|
1816
|
+
// src/hooks/directory-agents-injector/storage.ts
|
|
1817
|
+
function getStoragePath(sessionID) {
|
|
1818
|
+
return join6(AGENTS_INJECTOR_STORAGE, `${sessionID}.json`);
|
|
1819
|
+
}
|
|
1820
|
+
function loadInjectedPaths(sessionID) {
|
|
1821
|
+
const filePath = getStoragePath(sessionID);
|
|
1822
|
+
if (!existsSync5(filePath))
|
|
1823
|
+
return new Set;
|
|
1824
|
+
try {
|
|
1825
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
1826
|
+
const data = JSON.parse(content);
|
|
1827
|
+
return new Set(data.injectedPaths);
|
|
1828
|
+
} catch {
|
|
1829
|
+
return new Set;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
function saveInjectedPaths(sessionID, paths) {
|
|
1833
|
+
if (!existsSync5(AGENTS_INJECTOR_STORAGE)) {
|
|
1834
|
+
mkdirSync3(AGENTS_INJECTOR_STORAGE, { recursive: true });
|
|
1835
|
+
}
|
|
1836
|
+
const data = {
|
|
1837
|
+
sessionID,
|
|
1838
|
+
injectedPaths: [...paths],
|
|
1839
|
+
updatedAt: Date.now()
|
|
1840
|
+
};
|
|
1841
|
+
writeFileSync2(getStoragePath(sessionID), JSON.stringify(data, null, 2));
|
|
1842
|
+
}
|
|
1843
|
+
function clearInjectedPaths(sessionID) {
|
|
1844
|
+
const filePath = getStoragePath(sessionID);
|
|
1845
|
+
if (existsSync5(filePath)) {
|
|
1846
|
+
unlinkSync3(filePath);
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
// src/hooks/directory-agents-injector/index.ts
|
|
1851
|
+
function createDirectoryAgentsInjectorHook(ctx) {
|
|
1852
|
+
const sessionCaches = new Map;
|
|
1853
|
+
function getSessionCache(sessionID) {
|
|
1854
|
+
if (!sessionCaches.has(sessionID)) {
|
|
1855
|
+
sessionCaches.set(sessionID, loadInjectedPaths(sessionID));
|
|
1856
|
+
}
|
|
1857
|
+
return sessionCaches.get(sessionID);
|
|
1858
|
+
}
|
|
1859
|
+
function resolveFilePath(title) {
|
|
1860
|
+
if (!title)
|
|
1861
|
+
return null;
|
|
1862
|
+
if (title.startsWith("/"))
|
|
1863
|
+
return title;
|
|
1864
|
+
return resolve(ctx.directory, title);
|
|
1865
|
+
}
|
|
1866
|
+
function findAgentsMdUp(startDir) {
|
|
1867
|
+
const found = [];
|
|
1868
|
+
let current = startDir;
|
|
1869
|
+
while (true) {
|
|
1870
|
+
const agentsPath = join7(current, AGENTS_FILENAME);
|
|
1871
|
+
if (existsSync6(agentsPath)) {
|
|
1872
|
+
found.push(agentsPath);
|
|
1873
|
+
}
|
|
1874
|
+
if (current === ctx.directory)
|
|
1875
|
+
break;
|
|
1876
|
+
const parent = dirname2(current);
|
|
1877
|
+
if (parent === current)
|
|
1878
|
+
break;
|
|
1879
|
+
if (!parent.startsWith(ctx.directory))
|
|
1880
|
+
break;
|
|
1881
|
+
current = parent;
|
|
1882
|
+
}
|
|
1883
|
+
return found.reverse();
|
|
1884
|
+
}
|
|
1885
|
+
const toolExecuteAfter = async (input, output) => {
|
|
1886
|
+
if (input.tool.toLowerCase() !== "read")
|
|
1887
|
+
return;
|
|
1888
|
+
const filePath = resolveFilePath(output.title);
|
|
1889
|
+
if (!filePath)
|
|
1890
|
+
return;
|
|
1891
|
+
const dir = dirname2(filePath);
|
|
1892
|
+
const cache = getSessionCache(input.sessionID);
|
|
1893
|
+
const agentsPaths = findAgentsMdUp(dir);
|
|
1894
|
+
const toInject = [];
|
|
1895
|
+
for (const agentsPath of agentsPaths) {
|
|
1896
|
+
const agentsDir = dirname2(agentsPath);
|
|
1897
|
+
if (cache.has(agentsDir))
|
|
1898
|
+
continue;
|
|
1899
|
+
try {
|
|
1900
|
+
const content = readFileSync3(agentsPath, "utf-8");
|
|
1901
|
+
toInject.push({ path: agentsPath, content });
|
|
1902
|
+
cache.add(agentsDir);
|
|
1903
|
+
} catch {}
|
|
1904
|
+
}
|
|
1905
|
+
if (toInject.length === 0)
|
|
1906
|
+
return;
|
|
1907
|
+
for (const { path: path2, content } of toInject) {
|
|
1908
|
+
output.output += `
|
|
1909
|
+
|
|
1910
|
+
[Directory Context: ${path2}]
|
|
1911
|
+
${content}`;
|
|
1912
|
+
}
|
|
1913
|
+
saveInjectedPaths(input.sessionID, cache);
|
|
1914
|
+
};
|
|
1915
|
+
const eventHandler = async ({ event }) => {
|
|
1916
|
+
const props = event.properties;
|
|
1917
|
+
if (event.type === "session.deleted") {
|
|
1918
|
+
const sessionInfo = props?.info;
|
|
1919
|
+
if (sessionInfo?.id) {
|
|
1920
|
+
sessionCaches.delete(sessionInfo.id);
|
|
1921
|
+
clearInjectedPaths(sessionInfo.id);
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
if (event.type === "session.compacted") {
|
|
1925
|
+
const sessionID = props?.sessionID ?? props?.info?.id;
|
|
1926
|
+
if (sessionID) {
|
|
1927
|
+
sessionCaches.delete(sessionID);
|
|
1928
|
+
clearInjectedPaths(sessionID);
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
};
|
|
1932
|
+
return {
|
|
1933
|
+
"tool.execute.after": toolExecuteAfter,
|
|
1934
|
+
event: eventHandler
|
|
1935
|
+
};
|
|
1936
|
+
}
|
|
1937
|
+
// src/hooks/empty-task-response-detector.ts
|
|
1938
|
+
var EMPTY_RESPONSE_WARNING = `[Task Empty Response Warning]
|
|
1939
|
+
|
|
1940
|
+
Task invocation completed but returned no response. This indicates the agent either:
|
|
1941
|
+
- Failed to execute properly
|
|
1942
|
+
- Did not terminate correctly
|
|
1943
|
+
- Returned an empty result
|
|
1944
|
+
|
|
1945
|
+
Note: The call has already completed - you are NOT waiting for a response. Proceed accordingly.`;
|
|
1946
|
+
function createEmptyTaskResponseDetectorHook(_ctx) {
|
|
1947
|
+
return {
|
|
1948
|
+
"tool.execute.after": async (input, output) => {
|
|
1949
|
+
if (input.tool !== "Task")
|
|
1950
|
+
return;
|
|
1951
|
+
const responseText = output.output?.trim() ?? "";
|
|
1952
|
+
if (responseText === "") {
|
|
1953
|
+
output.output = EMPTY_RESPONSE_WARNING;
|
|
1762
1954
|
}
|
|
1763
1955
|
}
|
|
1764
1956
|
};
|
|
@@ -1988,14 +2180,14 @@ var EXT_TO_LANG = {
|
|
|
1988
2180
|
".tfvars": "terraform"
|
|
1989
2181
|
};
|
|
1990
2182
|
// src/tools/lsp/config.ts
|
|
1991
|
-
import { existsSync as
|
|
1992
|
-
import { join as
|
|
2183
|
+
import { existsSync as existsSync7, readFileSync as readFileSync4 } from "fs";
|
|
2184
|
+
import { join as join8 } from "path";
|
|
1993
2185
|
import { homedir as homedir2 } from "os";
|
|
1994
2186
|
function loadJsonFile(path2) {
|
|
1995
|
-
if (!
|
|
2187
|
+
if (!existsSync7(path2))
|
|
1996
2188
|
return null;
|
|
1997
2189
|
try {
|
|
1998
|
-
return JSON.parse(
|
|
2190
|
+
return JSON.parse(readFileSync4(path2, "utf-8"));
|
|
1999
2191
|
} catch {
|
|
2000
2192
|
return null;
|
|
2001
2193
|
}
|
|
@@ -2003,9 +2195,9 @@ function loadJsonFile(path2) {
|
|
|
2003
2195
|
function getConfigPaths() {
|
|
2004
2196
|
const cwd = process.cwd();
|
|
2005
2197
|
return {
|
|
2006
|
-
project:
|
|
2007
|
-
user:
|
|
2008
|
-
opencode:
|
|
2198
|
+
project: join8(cwd, ".opencode", "oh-my-opencode.json"),
|
|
2199
|
+
user: join8(homedir2(), ".config", "opencode", "oh-my-opencode.json"),
|
|
2200
|
+
opencode: join8(homedir2(), ".config", "opencode", "opencode.json")
|
|
2009
2201
|
};
|
|
2010
2202
|
}
|
|
2011
2203
|
function loadAllConfigs() {
|
|
@@ -2098,7 +2290,7 @@ function isServerInstalled(command) {
|
|
|
2098
2290
|
const pathEnv = process.env.PATH || "";
|
|
2099
2291
|
const paths = pathEnv.split(":");
|
|
2100
2292
|
for (const p of paths) {
|
|
2101
|
-
if (
|
|
2293
|
+
if (existsSync7(join8(p, cmd))) {
|
|
2102
2294
|
return true;
|
|
2103
2295
|
}
|
|
2104
2296
|
}
|
|
@@ -2148,8 +2340,8 @@ function getAllServers() {
|
|
|
2148
2340
|
}
|
|
2149
2341
|
// src/tools/lsp/client.ts
|
|
2150
2342
|
var {spawn: spawn3 } = globalThis.Bun;
|
|
2151
|
-
import { readFileSync as
|
|
2152
|
-
import { extname, resolve } from "path";
|
|
2343
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
2344
|
+
import { extname, resolve as resolve2 } from "path";
|
|
2153
2345
|
class LSPServerManager {
|
|
2154
2346
|
static instance;
|
|
2155
2347
|
clients = new Map;
|
|
@@ -2299,7 +2491,7 @@ class LSPClient {
|
|
|
2299
2491
|
}
|
|
2300
2492
|
this.startReading();
|
|
2301
2493
|
this.startStderrReading();
|
|
2302
|
-
await new Promise((
|
|
2494
|
+
await new Promise((resolve3) => setTimeout(resolve3, 100));
|
|
2303
2495
|
if (this.proc.exitCode !== null) {
|
|
2304
2496
|
const stderr = this.stderrBuffer.join(`
|
|
2305
2497
|
`);
|
|
@@ -2436,8 +2628,8 @@ stderr: ${stderr}` : ""));
|
|
|
2436
2628
|
\r
|
|
2437
2629
|
`;
|
|
2438
2630
|
this.proc.stdin.write(header + msg);
|
|
2439
|
-
return new Promise((
|
|
2440
|
-
this.pending.set(id, { resolve:
|
|
2631
|
+
return new Promise((resolve3, reject) => {
|
|
2632
|
+
this.pending.set(id, { resolve: resolve3, reject });
|
|
2441
2633
|
setTimeout(() => {
|
|
2442
2634
|
if (this.pending.has(id)) {
|
|
2443
2635
|
this.pending.delete(id);
|
|
@@ -2545,10 +2737,10 @@ ${msg}`);
|
|
|
2545
2737
|
await new Promise((r) => setTimeout(r, 300));
|
|
2546
2738
|
}
|
|
2547
2739
|
async openFile(filePath) {
|
|
2548
|
-
const absPath =
|
|
2740
|
+
const absPath = resolve2(filePath);
|
|
2549
2741
|
if (this.openedFiles.has(absPath))
|
|
2550
2742
|
return;
|
|
2551
|
-
const text =
|
|
2743
|
+
const text = readFileSync5(absPath, "utf-8");
|
|
2552
2744
|
const ext = extname(absPath);
|
|
2553
2745
|
const languageId = getLanguageId(ext);
|
|
2554
2746
|
this.notify("textDocument/didOpen", {
|
|
@@ -2563,7 +2755,7 @@ ${msg}`);
|
|
|
2563
2755
|
await new Promise((r) => setTimeout(r, 1000));
|
|
2564
2756
|
}
|
|
2565
2757
|
async hover(filePath, line, character) {
|
|
2566
|
-
const absPath =
|
|
2758
|
+
const absPath = resolve2(filePath);
|
|
2567
2759
|
await this.openFile(absPath);
|
|
2568
2760
|
return this.send("textDocument/hover", {
|
|
2569
2761
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -2571,7 +2763,7 @@ ${msg}`);
|
|
|
2571
2763
|
});
|
|
2572
2764
|
}
|
|
2573
2765
|
async definition(filePath, line, character) {
|
|
2574
|
-
const absPath =
|
|
2766
|
+
const absPath = resolve2(filePath);
|
|
2575
2767
|
await this.openFile(absPath);
|
|
2576
2768
|
return this.send("textDocument/definition", {
|
|
2577
2769
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -2579,7 +2771,7 @@ ${msg}`);
|
|
|
2579
2771
|
});
|
|
2580
2772
|
}
|
|
2581
2773
|
async references(filePath, line, character, includeDeclaration = true) {
|
|
2582
|
-
const absPath =
|
|
2774
|
+
const absPath = resolve2(filePath);
|
|
2583
2775
|
await this.openFile(absPath);
|
|
2584
2776
|
return this.send("textDocument/references", {
|
|
2585
2777
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -2588,7 +2780,7 @@ ${msg}`);
|
|
|
2588
2780
|
});
|
|
2589
2781
|
}
|
|
2590
2782
|
async documentSymbols(filePath) {
|
|
2591
|
-
const absPath =
|
|
2783
|
+
const absPath = resolve2(filePath);
|
|
2592
2784
|
await this.openFile(absPath);
|
|
2593
2785
|
return this.send("textDocument/documentSymbol", {
|
|
2594
2786
|
textDocument: { uri: `file://${absPath}` }
|
|
@@ -2598,7 +2790,7 @@ ${msg}`);
|
|
|
2598
2790
|
return this.send("workspace/symbol", { query });
|
|
2599
2791
|
}
|
|
2600
2792
|
async diagnostics(filePath) {
|
|
2601
|
-
const absPath =
|
|
2793
|
+
const absPath = resolve2(filePath);
|
|
2602
2794
|
const uri = `file://${absPath}`;
|
|
2603
2795
|
await this.openFile(absPath);
|
|
2604
2796
|
await new Promise((r) => setTimeout(r, 500));
|
|
@@ -2613,7 +2805,7 @@ ${msg}`);
|
|
|
2613
2805
|
return { items: this.diagnosticsStore.get(uri) ?? [] };
|
|
2614
2806
|
}
|
|
2615
2807
|
async prepareRename(filePath, line, character) {
|
|
2616
|
-
const absPath =
|
|
2808
|
+
const absPath = resolve2(filePath);
|
|
2617
2809
|
await this.openFile(absPath);
|
|
2618
2810
|
return this.send("textDocument/prepareRename", {
|
|
2619
2811
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -2621,7 +2813,7 @@ ${msg}`);
|
|
|
2621
2813
|
});
|
|
2622
2814
|
}
|
|
2623
2815
|
async rename(filePath, line, character, newName) {
|
|
2624
|
-
const absPath =
|
|
2816
|
+
const absPath = resolve2(filePath);
|
|
2625
2817
|
await this.openFile(absPath);
|
|
2626
2818
|
return this.send("textDocument/rename", {
|
|
2627
2819
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -2630,7 +2822,7 @@ ${msg}`);
|
|
|
2630
2822
|
});
|
|
2631
2823
|
}
|
|
2632
2824
|
async codeAction(filePath, startLine, startChar, endLine, endChar, only) {
|
|
2633
|
-
const absPath =
|
|
2825
|
+
const absPath = resolve2(filePath);
|
|
2634
2826
|
await this.openFile(absPath);
|
|
2635
2827
|
return this.send("textDocument/codeAction", {
|
|
2636
2828
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -2662,26 +2854,26 @@ ${msg}`);
|
|
|
2662
2854
|
}
|
|
2663
2855
|
}
|
|
2664
2856
|
// src/tools/lsp/utils.ts
|
|
2665
|
-
import { extname as extname2, resolve as
|
|
2666
|
-
import { existsSync as
|
|
2857
|
+
import { extname as extname2, resolve as resolve3 } from "path";
|
|
2858
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
2667
2859
|
function findWorkspaceRoot(filePath) {
|
|
2668
|
-
let dir =
|
|
2669
|
-
if (!
|
|
2860
|
+
let dir = resolve3(filePath);
|
|
2861
|
+
if (!existsSync8(dir) || !__require("fs").statSync(dir).isDirectory()) {
|
|
2670
2862
|
dir = __require("path").dirname(dir);
|
|
2671
2863
|
}
|
|
2672
2864
|
const markers = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
|
|
2673
2865
|
while (dir !== "/") {
|
|
2674
2866
|
for (const marker of markers) {
|
|
2675
|
-
if (
|
|
2867
|
+
if (existsSync8(__require("path").join(dir, marker))) {
|
|
2676
2868
|
return dir;
|
|
2677
2869
|
}
|
|
2678
2870
|
}
|
|
2679
2871
|
dir = __require("path").dirname(dir);
|
|
2680
2872
|
}
|
|
2681
|
-
return __require("path").dirname(
|
|
2873
|
+
return __require("path").dirname(resolve3(filePath));
|
|
2682
2874
|
}
|
|
2683
2875
|
async function withLspClient(filePath, fn) {
|
|
2684
|
-
const absPath =
|
|
2876
|
+
const absPath = resolve3(filePath);
|
|
2685
2877
|
const ext = extname2(absPath);
|
|
2686
2878
|
const server = findServerForExtension(ext);
|
|
2687
2879
|
if (!server) {
|
|
@@ -2830,7 +3022,7 @@ function formatCodeActions(actions) {
|
|
|
2830
3022
|
}
|
|
2831
3023
|
function applyTextEditsToFile(filePath, edits) {
|
|
2832
3024
|
try {
|
|
2833
|
-
let content =
|
|
3025
|
+
let content = readFileSync6(filePath, "utf-8");
|
|
2834
3026
|
const lines = content.split(`
|
|
2835
3027
|
`);
|
|
2836
3028
|
const sortedEdits = [...edits].sort((a, b) => {
|
|
@@ -2855,7 +3047,7 @@ function applyTextEditsToFile(filePath, edits) {
|
|
|
2855
3047
|
`));
|
|
2856
3048
|
}
|
|
2857
3049
|
}
|
|
2858
|
-
|
|
3050
|
+
writeFileSync3(filePath, lines.join(`
|
|
2859
3051
|
`), "utf-8");
|
|
2860
3052
|
return { success: true, editCount: edits.length };
|
|
2861
3053
|
} catch (err) {
|
|
@@ -2886,7 +3078,7 @@ function applyWorkspaceEdit(edit) {
|
|
|
2886
3078
|
if (change.kind === "create") {
|
|
2887
3079
|
try {
|
|
2888
3080
|
const filePath = change.uri.replace("file://", "");
|
|
2889
|
-
|
|
3081
|
+
writeFileSync3(filePath, "", "utf-8");
|
|
2890
3082
|
result.filesModified.push(filePath);
|
|
2891
3083
|
} catch (err) {
|
|
2892
3084
|
result.success = false;
|
|
@@ -2896,8 +3088,8 @@ function applyWorkspaceEdit(edit) {
|
|
|
2896
3088
|
try {
|
|
2897
3089
|
const oldPath = change.oldUri.replace("file://", "");
|
|
2898
3090
|
const newPath = change.newUri.replace("file://", "");
|
|
2899
|
-
const content =
|
|
2900
|
-
|
|
3091
|
+
const content = readFileSync6(oldPath, "utf-8");
|
|
3092
|
+
writeFileSync3(newPath, content, "utf-8");
|
|
2901
3093
|
__require("fs").unlinkSync(oldPath);
|
|
2902
3094
|
result.filesModified.push(newPath);
|
|
2903
3095
|
} catch (err) {
|
|
@@ -15597,13 +15789,13 @@ var lsp_code_action_resolve = tool({
|
|
|
15597
15789
|
});
|
|
15598
15790
|
// src/tools/ast-grep/constants.ts
|
|
15599
15791
|
import { createRequire as createRequire4 } from "module";
|
|
15600
|
-
import { dirname as
|
|
15601
|
-
import { existsSync as
|
|
15792
|
+
import { dirname as dirname3, join as join10 } from "path";
|
|
15793
|
+
import { existsSync as existsSync10, statSync } from "fs";
|
|
15602
15794
|
|
|
15603
15795
|
// src/tools/ast-grep/downloader.ts
|
|
15604
15796
|
var {spawn: spawn4 } = globalThis.Bun;
|
|
15605
|
-
import { existsSync as
|
|
15606
|
-
import { join as
|
|
15797
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, chmodSync as chmodSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
15798
|
+
import { join as join9 } from "path";
|
|
15607
15799
|
import { homedir as homedir3 } from "os";
|
|
15608
15800
|
import { createRequire as createRequire3 } from "module";
|
|
15609
15801
|
var REPO2 = "ast-grep/ast-grep";
|
|
@@ -15629,19 +15821,19 @@ var PLATFORM_MAP2 = {
|
|
|
15629
15821
|
function getCacheDir2() {
|
|
15630
15822
|
if (process.platform === "win32") {
|
|
15631
15823
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
15632
|
-
const base2 = localAppData ||
|
|
15633
|
-
return
|
|
15824
|
+
const base2 = localAppData || join9(homedir3(), "AppData", "Local");
|
|
15825
|
+
return join9(base2, "oh-my-opencode", "bin");
|
|
15634
15826
|
}
|
|
15635
15827
|
const xdgCache2 = process.env.XDG_CACHE_HOME;
|
|
15636
|
-
const base = xdgCache2 ||
|
|
15637
|
-
return
|
|
15828
|
+
const base = xdgCache2 || join9(homedir3(), ".cache");
|
|
15829
|
+
return join9(base, "oh-my-opencode", "bin");
|
|
15638
15830
|
}
|
|
15639
15831
|
function getBinaryName3() {
|
|
15640
15832
|
return process.platform === "win32" ? "sg.exe" : "sg";
|
|
15641
15833
|
}
|
|
15642
15834
|
function getCachedBinaryPath2() {
|
|
15643
|
-
const binaryPath =
|
|
15644
|
-
return
|
|
15835
|
+
const binaryPath = join9(getCacheDir2(), getBinaryName3());
|
|
15836
|
+
return existsSync9(binaryPath) ? binaryPath : null;
|
|
15645
15837
|
}
|
|
15646
15838
|
async function extractZip2(archivePath, destDir) {
|
|
15647
15839
|
const proc = process.platform === "win32" ? spawn4([
|
|
@@ -15667,8 +15859,8 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
|
|
|
15667
15859
|
}
|
|
15668
15860
|
const cacheDir = getCacheDir2();
|
|
15669
15861
|
const binaryName = getBinaryName3();
|
|
15670
|
-
const binaryPath =
|
|
15671
|
-
if (
|
|
15862
|
+
const binaryPath = join9(cacheDir, binaryName);
|
|
15863
|
+
if (existsSync9(binaryPath)) {
|
|
15672
15864
|
return binaryPath;
|
|
15673
15865
|
}
|
|
15674
15866
|
const { arch, os: os2 } = platformInfo;
|
|
@@ -15676,21 +15868,21 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
|
|
|
15676
15868
|
const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
|
|
15677
15869
|
console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
|
|
15678
15870
|
try {
|
|
15679
|
-
if (!
|
|
15680
|
-
|
|
15871
|
+
if (!existsSync9(cacheDir)) {
|
|
15872
|
+
mkdirSync4(cacheDir, { recursive: true });
|
|
15681
15873
|
}
|
|
15682
15874
|
const response = await fetch(downloadUrl, { redirect: "follow" });
|
|
15683
15875
|
if (!response.ok) {
|
|
15684
15876
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
15685
15877
|
}
|
|
15686
|
-
const archivePath =
|
|
15878
|
+
const archivePath = join9(cacheDir, assetName);
|
|
15687
15879
|
const arrayBuffer = await response.arrayBuffer();
|
|
15688
15880
|
await Bun.write(archivePath, arrayBuffer);
|
|
15689
15881
|
await extractZip2(archivePath, cacheDir);
|
|
15690
|
-
if (
|
|
15691
|
-
|
|
15882
|
+
if (existsSync9(archivePath)) {
|
|
15883
|
+
unlinkSync4(archivePath);
|
|
15692
15884
|
}
|
|
15693
|
-
if (process.platform !== "win32" &&
|
|
15885
|
+
if (process.platform !== "win32" && existsSync9(binaryPath)) {
|
|
15694
15886
|
chmodSync2(binaryPath, 493);
|
|
15695
15887
|
}
|
|
15696
15888
|
console.log(`[oh-my-opencode] ast-grep binary ready.`);
|
|
@@ -15740,9 +15932,9 @@ function findSgCliPathSync() {
|
|
|
15740
15932
|
try {
|
|
15741
15933
|
const require2 = createRequire4(import.meta.url);
|
|
15742
15934
|
const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
|
|
15743
|
-
const cliDir =
|
|
15744
|
-
const sgPath =
|
|
15745
|
-
if (
|
|
15935
|
+
const cliDir = dirname3(cliPkgPath);
|
|
15936
|
+
const sgPath = join10(cliDir, binaryName);
|
|
15937
|
+
if (existsSync10(sgPath) && isValidBinary(sgPath)) {
|
|
15746
15938
|
return sgPath;
|
|
15747
15939
|
}
|
|
15748
15940
|
} catch {}
|
|
@@ -15751,10 +15943,10 @@ function findSgCliPathSync() {
|
|
|
15751
15943
|
try {
|
|
15752
15944
|
const require2 = createRequire4(import.meta.url);
|
|
15753
15945
|
const pkgPath = require2.resolve(`${platformPkg}/package.json`);
|
|
15754
|
-
const pkgDir =
|
|
15946
|
+
const pkgDir = dirname3(pkgPath);
|
|
15755
15947
|
const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
15756
|
-
const binaryPath =
|
|
15757
|
-
if (
|
|
15948
|
+
const binaryPath = join10(pkgDir, astGrepName);
|
|
15949
|
+
if (existsSync10(binaryPath) && isValidBinary(binaryPath)) {
|
|
15758
15950
|
return binaryPath;
|
|
15759
15951
|
}
|
|
15760
15952
|
} catch {}
|
|
@@ -15762,7 +15954,7 @@ function findSgCliPathSync() {
|
|
|
15762
15954
|
if (process.platform === "darwin") {
|
|
15763
15955
|
const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
|
|
15764
15956
|
for (const path2 of homebrewPaths) {
|
|
15765
|
-
if (
|
|
15957
|
+
if (existsSync10(path2) && isValidBinary(path2)) {
|
|
15766
15958
|
return path2;
|
|
15767
15959
|
}
|
|
15768
15960
|
}
|
|
@@ -15846,11 +16038,11 @@ var LANG_EXTENSIONS = {
|
|
|
15846
16038
|
|
|
15847
16039
|
// src/tools/ast-grep/cli.ts
|
|
15848
16040
|
var {spawn: spawn5 } = globalThis.Bun;
|
|
15849
|
-
import { existsSync as
|
|
16041
|
+
import { existsSync as existsSync11 } from "fs";
|
|
15850
16042
|
var resolvedCliPath3 = null;
|
|
15851
16043
|
var initPromise2 = null;
|
|
15852
16044
|
async function getAstGrepPath() {
|
|
15853
|
-
if (resolvedCliPath3 !== null &&
|
|
16045
|
+
if (resolvedCliPath3 !== null && existsSync11(resolvedCliPath3)) {
|
|
15854
16046
|
return resolvedCliPath3;
|
|
15855
16047
|
}
|
|
15856
16048
|
if (initPromise2) {
|
|
@@ -15858,7 +16050,7 @@ async function getAstGrepPath() {
|
|
|
15858
16050
|
}
|
|
15859
16051
|
initPromise2 = (async () => {
|
|
15860
16052
|
const syncPath = findSgCliPathSync();
|
|
15861
|
-
if (syncPath &&
|
|
16053
|
+
if (syncPath && existsSync11(syncPath)) {
|
|
15862
16054
|
resolvedCliPath3 = syncPath;
|
|
15863
16055
|
setSgCliPath(syncPath);
|
|
15864
16056
|
return syncPath;
|
|
@@ -15892,7 +16084,7 @@ async function runSg(options) {
|
|
|
15892
16084
|
const paths = options.paths && options.paths.length > 0 ? options.paths : ["."];
|
|
15893
16085
|
args.push(...paths);
|
|
15894
16086
|
let cliPath = getSgCliPath();
|
|
15895
|
-
if (!
|
|
16087
|
+
if (!existsSync11(cliPath) && cliPath !== "sg") {
|
|
15896
16088
|
const downloadedPath = await getAstGrepPath();
|
|
15897
16089
|
if (downloadedPath) {
|
|
15898
16090
|
cliPath = downloadedPath;
|
|
@@ -16333,12 +16525,12 @@ var ast_grep_transform = tool({
|
|
|
16333
16525
|
}
|
|
16334
16526
|
}
|
|
16335
16527
|
});
|
|
16336
|
-
// src/tools/
|
|
16528
|
+
// src/tools/grep/cli.ts
|
|
16337
16529
|
var {spawn: spawn6 } = globalThis.Bun;
|
|
16338
16530
|
|
|
16339
|
-
// src/tools/
|
|
16340
|
-
import { existsSync as
|
|
16341
|
-
import { join as
|
|
16531
|
+
// src/tools/grep/constants.ts
|
|
16532
|
+
import { existsSync as existsSync12 } from "fs";
|
|
16533
|
+
import { join as join11, dirname as dirname4 } from "path";
|
|
16342
16534
|
import { spawnSync } from "child_process";
|
|
16343
16535
|
var cachedCli = null;
|
|
16344
16536
|
function findExecutable(name) {
|
|
@@ -16355,17 +16547,17 @@ function findExecutable(name) {
|
|
|
16355
16547
|
}
|
|
16356
16548
|
function getOpenCodeBundledRg() {
|
|
16357
16549
|
const execPath = process.execPath;
|
|
16358
|
-
const execDir =
|
|
16550
|
+
const execDir = dirname4(execPath);
|
|
16359
16551
|
const isWindows = process.platform === "win32";
|
|
16360
16552
|
const rgName = isWindows ? "rg.exe" : "rg";
|
|
16361
16553
|
const candidates = [
|
|
16362
|
-
|
|
16363
|
-
|
|
16364
|
-
|
|
16365
|
-
|
|
16554
|
+
join11(execDir, rgName),
|
|
16555
|
+
join11(execDir, "bin", rgName),
|
|
16556
|
+
join11(execDir, "..", "bin", rgName),
|
|
16557
|
+
join11(execDir, "..", "libexec", rgName)
|
|
16366
16558
|
];
|
|
16367
16559
|
for (const candidate of candidates) {
|
|
16368
|
-
if (
|
|
16560
|
+
if (existsSync12(candidate)) {
|
|
16369
16561
|
return candidate;
|
|
16370
16562
|
}
|
|
16371
16563
|
}
|
|
@@ -16407,7 +16599,7 @@ var RG_SAFETY_FLAGS = [
|
|
|
16407
16599
|
];
|
|
16408
16600
|
var GREP_SAFETY_FLAGS = ["-n", "-H", "--color=never"];
|
|
16409
16601
|
|
|
16410
|
-
// src/tools/
|
|
16602
|
+
// src/tools/grep/cli.ts
|
|
16411
16603
|
function buildRgArgs(options) {
|
|
16412
16604
|
const args = [
|
|
16413
16605
|
...RG_SAFETY_FLAGS,
|
|
@@ -16551,7 +16743,7 @@ async function runRg(options) {
|
|
|
16551
16743
|
}
|
|
16552
16744
|
}
|
|
16553
16745
|
|
|
16554
|
-
// src/tools/
|
|
16746
|
+
// src/tools/grep/utils.ts
|
|
16555
16747
|
function formatGrepResult(result) {
|
|
16556
16748
|
if (result.error) {
|
|
16557
16749
|
return `Error: ${result.error}`;
|
|
@@ -16582,8 +16774,8 @@ function formatGrepResult(result) {
|
|
|
16582
16774
|
`);
|
|
16583
16775
|
}
|
|
16584
16776
|
|
|
16585
|
-
// src/tools/
|
|
16586
|
-
var
|
|
16777
|
+
// src/tools/grep/tools.ts
|
|
16778
|
+
var grep = tool({
|
|
16587
16779
|
description: "Fast content search tool with safety limits (60s timeout, 10MB output). " + "Searches file contents using regular expressions. " + 'Supports full regex syntax (eg. "log.*Error", "function\\s+\\w+", etc.). ' + 'Filter files by pattern with the include parameter (eg. "*.js", "*.{ts,tsx}"). ' + "Returns file paths with matches sorted by modification time.",
|
|
16588
16780
|
args: {
|
|
16589
16781
|
pattern: tool.schema.string().describe("The regex pattern to search for in file contents"),
|
|
@@ -16607,6 +16799,162 @@ var safe_grep = tool({
|
|
|
16607
16799
|
}
|
|
16608
16800
|
});
|
|
16609
16801
|
|
|
16802
|
+
// src/tools/glob/cli.ts
|
|
16803
|
+
var {spawn: spawn7 } = globalThis.Bun;
|
|
16804
|
+
|
|
16805
|
+
// src/tools/glob/constants.ts
|
|
16806
|
+
var DEFAULT_TIMEOUT_MS3 = 60000;
|
|
16807
|
+
var DEFAULT_LIMIT = 100;
|
|
16808
|
+
var DEFAULT_MAX_DEPTH2 = 20;
|
|
16809
|
+
var DEFAULT_MAX_OUTPUT_BYTES3 = 10 * 1024 * 1024;
|
|
16810
|
+
var RG_FILES_FLAGS = [
|
|
16811
|
+
"--files",
|
|
16812
|
+
"--color=never",
|
|
16813
|
+
"--glob=!.git/*"
|
|
16814
|
+
];
|
|
16815
|
+
|
|
16816
|
+
// src/tools/glob/cli.ts
|
|
16817
|
+
import { stat } from "fs/promises";
|
|
16818
|
+
function buildRgArgs2(options) {
|
|
16819
|
+
const args = [
|
|
16820
|
+
...RG_FILES_FLAGS,
|
|
16821
|
+
`--max-depth=${Math.min(options.maxDepth ?? DEFAULT_MAX_DEPTH2, DEFAULT_MAX_DEPTH2)}`
|
|
16822
|
+
];
|
|
16823
|
+
if (options.hidden)
|
|
16824
|
+
args.push("--hidden");
|
|
16825
|
+
if (options.noIgnore)
|
|
16826
|
+
args.push("--no-ignore");
|
|
16827
|
+
args.push(`--glob=${options.pattern}`);
|
|
16828
|
+
return args;
|
|
16829
|
+
}
|
|
16830
|
+
function buildFindArgs(options) {
|
|
16831
|
+
const args = ["."];
|
|
16832
|
+
const maxDepth = Math.min(options.maxDepth ?? DEFAULT_MAX_DEPTH2, DEFAULT_MAX_DEPTH2);
|
|
16833
|
+
args.push("-maxdepth", String(maxDepth));
|
|
16834
|
+
args.push("-type", "f");
|
|
16835
|
+
args.push("-name", options.pattern);
|
|
16836
|
+
if (!options.hidden) {
|
|
16837
|
+
args.push("-not", "-path", "*/.*");
|
|
16838
|
+
}
|
|
16839
|
+
return args;
|
|
16840
|
+
}
|
|
16841
|
+
async function getFileMtime(filePath) {
|
|
16842
|
+
try {
|
|
16843
|
+
const stats = await stat(filePath);
|
|
16844
|
+
return stats.mtime.getTime();
|
|
16845
|
+
} catch {
|
|
16846
|
+
return 0;
|
|
16847
|
+
}
|
|
16848
|
+
}
|
|
16849
|
+
async function runRgFiles(options) {
|
|
16850
|
+
const cli = resolveGrepCli();
|
|
16851
|
+
const timeout = Math.min(options.timeout ?? DEFAULT_TIMEOUT_MS3, DEFAULT_TIMEOUT_MS3);
|
|
16852
|
+
const limit = Math.min(options.limit ?? DEFAULT_LIMIT, DEFAULT_LIMIT);
|
|
16853
|
+
const isRg = cli.backend === "rg";
|
|
16854
|
+
const args = isRg ? buildRgArgs2(options) : buildFindArgs(options);
|
|
16855
|
+
const paths = options.paths?.length ? options.paths : ["."];
|
|
16856
|
+
if (isRg) {
|
|
16857
|
+
args.push(...paths);
|
|
16858
|
+
}
|
|
16859
|
+
const cwd = paths[0] || ".";
|
|
16860
|
+
const proc = spawn7([cli.path, ...args], {
|
|
16861
|
+
stdout: "pipe",
|
|
16862
|
+
stderr: "pipe",
|
|
16863
|
+
cwd: isRg ? undefined : cwd
|
|
16864
|
+
});
|
|
16865
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
16866
|
+
const id = setTimeout(() => {
|
|
16867
|
+
proc.kill();
|
|
16868
|
+
reject(new Error(`Glob search timeout after ${timeout}ms`));
|
|
16869
|
+
}, timeout);
|
|
16870
|
+
proc.exited.then(() => clearTimeout(id));
|
|
16871
|
+
});
|
|
16872
|
+
try {
|
|
16873
|
+
const stdout = await Promise.race([new Response(proc.stdout).text(), timeoutPromise]);
|
|
16874
|
+
const stderr = await new Response(proc.stderr).text();
|
|
16875
|
+
const exitCode = await proc.exited;
|
|
16876
|
+
if (exitCode > 1 && stderr.trim()) {
|
|
16877
|
+
return {
|
|
16878
|
+
files: [],
|
|
16879
|
+
totalFiles: 0,
|
|
16880
|
+
truncated: false,
|
|
16881
|
+
error: stderr.trim()
|
|
16882
|
+
};
|
|
16883
|
+
}
|
|
16884
|
+
const truncatedOutput = stdout.length >= DEFAULT_MAX_OUTPUT_BYTES3;
|
|
16885
|
+
const outputToProcess = truncatedOutput ? stdout.substring(0, DEFAULT_MAX_OUTPUT_BYTES3) : stdout;
|
|
16886
|
+
const lines = outputToProcess.trim().split(`
|
|
16887
|
+
`).filter(Boolean);
|
|
16888
|
+
const files = [];
|
|
16889
|
+
let truncated = false;
|
|
16890
|
+
for (const line of lines) {
|
|
16891
|
+
if (files.length >= limit) {
|
|
16892
|
+
truncated = true;
|
|
16893
|
+
break;
|
|
16894
|
+
}
|
|
16895
|
+
const filePath = isRg ? line : `${cwd}/${line}`;
|
|
16896
|
+
const mtime = await getFileMtime(filePath);
|
|
16897
|
+
files.push({ path: filePath, mtime });
|
|
16898
|
+
}
|
|
16899
|
+
files.sort((a, b) => b.mtime - a.mtime);
|
|
16900
|
+
return {
|
|
16901
|
+
files,
|
|
16902
|
+
totalFiles: files.length,
|
|
16903
|
+
truncated: truncated || truncatedOutput
|
|
16904
|
+
};
|
|
16905
|
+
} catch (e) {
|
|
16906
|
+
return {
|
|
16907
|
+
files: [],
|
|
16908
|
+
totalFiles: 0,
|
|
16909
|
+
truncated: false,
|
|
16910
|
+
error: e instanceof Error ? e.message : String(e)
|
|
16911
|
+
};
|
|
16912
|
+
}
|
|
16913
|
+
}
|
|
16914
|
+
|
|
16915
|
+
// src/tools/glob/utils.ts
|
|
16916
|
+
function formatGlobResult(result) {
|
|
16917
|
+
if (result.error) {
|
|
16918
|
+
return `Error: ${result.error}`;
|
|
16919
|
+
}
|
|
16920
|
+
if (result.files.length === 0) {
|
|
16921
|
+
return "No files found";
|
|
16922
|
+
}
|
|
16923
|
+
const lines = [];
|
|
16924
|
+
lines.push(`Found ${result.totalFiles} file(s)`);
|
|
16925
|
+
lines.push("");
|
|
16926
|
+
for (const file2 of result.files) {
|
|
16927
|
+
lines.push(file2.path);
|
|
16928
|
+
}
|
|
16929
|
+
if (result.truncated) {
|
|
16930
|
+
lines.push("");
|
|
16931
|
+
lines.push("(Results are truncated. Consider using a more specific path or pattern.)");
|
|
16932
|
+
}
|
|
16933
|
+
return lines.join(`
|
|
16934
|
+
`);
|
|
16935
|
+
}
|
|
16936
|
+
|
|
16937
|
+
// src/tools/glob/tools.ts
|
|
16938
|
+
var glob = tool({
|
|
16939
|
+
description: "Fast file pattern matching tool with safety limits (60s timeout, 100 file limit). " + 'Supports glob patterns like "**/*.js" or "src/**/*.ts". ' + "Returns matching file paths sorted by modification time. " + "Use this tool when you need to find files by name patterns.",
|
|
16940
|
+
args: {
|
|
16941
|
+
pattern: tool.schema.string().describe("The glob pattern to match files against"),
|
|
16942
|
+
path: tool.schema.string().optional().describe("The directory to search in. If not specified, the current working directory will be used. " + 'IMPORTANT: Omit this field to use the default directory. DO NOT enter "undefined" or "null" - ' + "simply omit it for the default behavior. Must be a valid directory path if provided.")
|
|
16943
|
+
},
|
|
16944
|
+
execute: async (args) => {
|
|
16945
|
+
try {
|
|
16946
|
+
const paths = args.path ? [args.path] : undefined;
|
|
16947
|
+
const result = await runRgFiles({
|
|
16948
|
+
pattern: args.pattern,
|
|
16949
|
+
paths
|
|
16950
|
+
});
|
|
16951
|
+
return formatGlobResult(result);
|
|
16952
|
+
} catch (e) {
|
|
16953
|
+
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
16954
|
+
}
|
|
16955
|
+
}
|
|
16956
|
+
});
|
|
16957
|
+
|
|
16610
16958
|
// src/tools/index.ts
|
|
16611
16959
|
var builtinTools = {
|
|
16612
16960
|
lsp_hover,
|
|
@@ -16622,7 +16970,8 @@ var builtinTools = {
|
|
|
16622
16970
|
lsp_code_action_resolve,
|
|
16623
16971
|
ast_grep_search,
|
|
16624
16972
|
ast_grep_replace,
|
|
16625
|
-
|
|
16973
|
+
grep,
|
|
16974
|
+
glob
|
|
16626
16975
|
};
|
|
16627
16976
|
|
|
16628
16977
|
// src/mcp/websearch-exa.ts
|
|
@@ -16730,6 +17079,8 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
16730
17079
|
const pulseMonitor = createPulseMonitorHook(ctx);
|
|
16731
17080
|
const commentChecker = createCommentCheckerHooks();
|
|
16732
17081
|
const grepOutputTruncator = createGrepOutputTruncatorHook(ctx);
|
|
17082
|
+
const directoryAgentsInjector = createDirectoryAgentsInjectorHook(ctx);
|
|
17083
|
+
const emptyTaskResponseDetector = createEmptyTaskResponseDetectorHook(ctx);
|
|
16733
17084
|
updateTerminalTitle({ sessionId: "main" });
|
|
16734
17085
|
const pluginConfig = loadPluginConfig(ctx.directory);
|
|
16735
17086
|
let mainSessionID;
|
|
@@ -16744,8 +17095,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
16744
17095
|
...agents
|
|
16745
17096
|
};
|
|
16746
17097
|
config3.tools = {
|
|
16747
|
-
...config3.tools
|
|
16748
|
-
grep: false
|
|
17098
|
+
...config3.tools
|
|
16749
17099
|
};
|
|
16750
17100
|
config3.mcp = {
|
|
16751
17101
|
...config3.mcp,
|
|
@@ -16756,6 +17106,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
16756
17106
|
await todoContinuationEnforcer(input);
|
|
16757
17107
|
await contextWindowMonitor.event(input);
|
|
16758
17108
|
await pulseMonitor.event(input);
|
|
17109
|
+
await directoryAgentsInjector.event(input);
|
|
16759
17110
|
const { event } = input;
|
|
16760
17111
|
const props = event.properties;
|
|
16761
17112
|
if (event.type === "session.created") {
|
|
@@ -16855,6 +17206,8 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
16855
17206
|
await grepOutputTruncator["tool.execute.after"](input, output);
|
|
16856
17207
|
await contextWindowMonitor["tool.execute.after"](input, output);
|
|
16857
17208
|
await commentChecker["tool.execute.after"](input, output);
|
|
17209
|
+
await directoryAgentsInjector["tool.execute.after"](input, output);
|
|
17210
|
+
await emptyTaskResponseDetector["tool.execute.after"](input, output);
|
|
16858
17211
|
if (input.sessionID === mainSessionID) {
|
|
16859
17212
|
updateTerminalTitle({
|
|
16860
17213
|
sessionId: input.sessionID,
|