oh-my-opencode 0.1.26 → 0.1.27
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/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 +635 -278
- 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;
|
|
@@ -1763,6 +1797,168 @@ function createPulseMonitorHook(ctx) {
|
|
|
1763
1797
|
}
|
|
1764
1798
|
};
|
|
1765
1799
|
}
|
|
1800
|
+
// src/hooks/directory-agents-injector/index.ts
|
|
1801
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
|
|
1802
|
+
import { dirname as dirname2, join as join7, resolve } from "path";
|
|
1803
|
+
|
|
1804
|
+
// src/hooks/directory-agents-injector/storage.ts
|
|
1805
|
+
import {
|
|
1806
|
+
existsSync as existsSync5,
|
|
1807
|
+
mkdirSync as mkdirSync3,
|
|
1808
|
+
readFileSync as readFileSync2,
|
|
1809
|
+
writeFileSync as writeFileSync2,
|
|
1810
|
+
unlinkSync as unlinkSync3
|
|
1811
|
+
} from "fs";
|
|
1812
|
+
import { join as join6 } from "path";
|
|
1813
|
+
|
|
1814
|
+
// src/hooks/directory-agents-injector/constants.ts
|
|
1815
|
+
import { join as join5 } from "path";
|
|
1816
|
+
var OPENCODE_STORAGE2 = join5(xdgData ?? "", "opencode", "storage");
|
|
1817
|
+
var AGENTS_INJECTOR_STORAGE = join5(OPENCODE_STORAGE2, "directory-agents");
|
|
1818
|
+
var AGENTS_FILENAME = "AGENTS.md";
|
|
1819
|
+
|
|
1820
|
+
// src/hooks/directory-agents-injector/storage.ts
|
|
1821
|
+
function getStoragePath(sessionID) {
|
|
1822
|
+
return join6(AGENTS_INJECTOR_STORAGE, `${sessionID}.json`);
|
|
1823
|
+
}
|
|
1824
|
+
function loadInjectedPaths(sessionID) {
|
|
1825
|
+
const filePath = getStoragePath(sessionID);
|
|
1826
|
+
if (!existsSync5(filePath))
|
|
1827
|
+
return new Set;
|
|
1828
|
+
try {
|
|
1829
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
1830
|
+
const data = JSON.parse(content);
|
|
1831
|
+
return new Set(data.injectedPaths);
|
|
1832
|
+
} catch {
|
|
1833
|
+
return new Set;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
function saveInjectedPaths(sessionID, paths) {
|
|
1837
|
+
if (!existsSync5(AGENTS_INJECTOR_STORAGE)) {
|
|
1838
|
+
mkdirSync3(AGENTS_INJECTOR_STORAGE, { recursive: true });
|
|
1839
|
+
}
|
|
1840
|
+
const data = {
|
|
1841
|
+
sessionID,
|
|
1842
|
+
injectedPaths: [...paths],
|
|
1843
|
+
updatedAt: Date.now()
|
|
1844
|
+
};
|
|
1845
|
+
writeFileSync2(getStoragePath(sessionID), JSON.stringify(data, null, 2));
|
|
1846
|
+
}
|
|
1847
|
+
function clearInjectedPaths(sessionID) {
|
|
1848
|
+
const filePath = getStoragePath(sessionID);
|
|
1849
|
+
if (existsSync5(filePath)) {
|
|
1850
|
+
unlinkSync3(filePath);
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
// src/hooks/directory-agents-injector/index.ts
|
|
1855
|
+
function createDirectoryAgentsInjectorHook(ctx) {
|
|
1856
|
+
const sessionCaches = new Map;
|
|
1857
|
+
function getSessionCache(sessionID) {
|
|
1858
|
+
if (!sessionCaches.has(sessionID)) {
|
|
1859
|
+
sessionCaches.set(sessionID, loadInjectedPaths(sessionID));
|
|
1860
|
+
}
|
|
1861
|
+
return sessionCaches.get(sessionID);
|
|
1862
|
+
}
|
|
1863
|
+
function resolveFilePath(title) {
|
|
1864
|
+
if (!title)
|
|
1865
|
+
return null;
|
|
1866
|
+
if (title.startsWith("/"))
|
|
1867
|
+
return title;
|
|
1868
|
+
return resolve(ctx.directory, title);
|
|
1869
|
+
}
|
|
1870
|
+
function findAgentsMdUp(startDir) {
|
|
1871
|
+
const found = [];
|
|
1872
|
+
let current = startDir;
|
|
1873
|
+
while (true) {
|
|
1874
|
+
const agentsPath = join7(current, AGENTS_FILENAME);
|
|
1875
|
+
if (existsSync6(agentsPath)) {
|
|
1876
|
+
found.push(agentsPath);
|
|
1877
|
+
}
|
|
1878
|
+
if (current === ctx.directory)
|
|
1879
|
+
break;
|
|
1880
|
+
const parent = dirname2(current);
|
|
1881
|
+
if (parent === current)
|
|
1882
|
+
break;
|
|
1883
|
+
if (!parent.startsWith(ctx.directory))
|
|
1884
|
+
break;
|
|
1885
|
+
current = parent;
|
|
1886
|
+
}
|
|
1887
|
+
return found.reverse();
|
|
1888
|
+
}
|
|
1889
|
+
const toolExecuteAfter = async (input, output) => {
|
|
1890
|
+
if (input.tool.toLowerCase() !== "read")
|
|
1891
|
+
return;
|
|
1892
|
+
const filePath = resolveFilePath(output.title);
|
|
1893
|
+
if (!filePath)
|
|
1894
|
+
return;
|
|
1895
|
+
const dir = dirname2(filePath);
|
|
1896
|
+
const cache = getSessionCache(input.sessionID);
|
|
1897
|
+
const agentsPaths = findAgentsMdUp(dir);
|
|
1898
|
+
const toInject = [];
|
|
1899
|
+
for (const agentsPath of agentsPaths) {
|
|
1900
|
+
const agentsDir = dirname2(agentsPath);
|
|
1901
|
+
if (cache.has(agentsDir))
|
|
1902
|
+
continue;
|
|
1903
|
+
try {
|
|
1904
|
+
const content = readFileSync3(agentsPath, "utf-8");
|
|
1905
|
+
toInject.push({ path: agentsPath, content });
|
|
1906
|
+
cache.add(agentsDir);
|
|
1907
|
+
} catch {}
|
|
1908
|
+
}
|
|
1909
|
+
if (toInject.length === 0)
|
|
1910
|
+
return;
|
|
1911
|
+
for (const { path: path2, content } of toInject) {
|
|
1912
|
+
output.output += `
|
|
1913
|
+
|
|
1914
|
+
[Directory Context: ${path2}]
|
|
1915
|
+
${content}`;
|
|
1916
|
+
}
|
|
1917
|
+
saveInjectedPaths(input.sessionID, cache);
|
|
1918
|
+
};
|
|
1919
|
+
const eventHandler = async ({ event }) => {
|
|
1920
|
+
const props = event.properties;
|
|
1921
|
+
if (event.type === "session.deleted") {
|
|
1922
|
+
const sessionInfo = props?.info;
|
|
1923
|
+
if (sessionInfo?.id) {
|
|
1924
|
+
sessionCaches.delete(sessionInfo.id);
|
|
1925
|
+
clearInjectedPaths(sessionInfo.id);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
if (event.type === "session.compacted") {
|
|
1929
|
+
const sessionID = props?.sessionID ?? props?.info?.id;
|
|
1930
|
+
if (sessionID) {
|
|
1931
|
+
sessionCaches.delete(sessionID);
|
|
1932
|
+
clearInjectedPaths(sessionID);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
};
|
|
1936
|
+
return {
|
|
1937
|
+
"tool.execute.after": toolExecuteAfter,
|
|
1938
|
+
event: eventHandler
|
|
1939
|
+
};
|
|
1940
|
+
}
|
|
1941
|
+
// src/hooks/empty-task-response-detector.ts
|
|
1942
|
+
var EMPTY_RESPONSE_WARNING = `[Task Empty Response Warning]
|
|
1943
|
+
|
|
1944
|
+
Task invocation completed but returned no response. This indicates the agent either:
|
|
1945
|
+
- Failed to execute properly
|
|
1946
|
+
- Did not terminate correctly
|
|
1947
|
+
- Returned an empty result
|
|
1948
|
+
|
|
1949
|
+
Note: The call has already completed - you are NOT waiting for a response. Proceed accordingly.`;
|
|
1950
|
+
function createEmptyTaskResponseDetectorHook(_ctx) {
|
|
1951
|
+
return {
|
|
1952
|
+
"tool.execute.after": async (input, output) => {
|
|
1953
|
+
if (input.tool !== "Task")
|
|
1954
|
+
return;
|
|
1955
|
+
const responseText = output.output?.trim() ?? "";
|
|
1956
|
+
if (responseText === "") {
|
|
1957
|
+
output.output = EMPTY_RESPONSE_WARNING;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1766
1962
|
// src/features/terminal/title.ts
|
|
1767
1963
|
var STATUS_ICONS = {
|
|
1768
1964
|
ready: "",
|
|
@@ -1988,14 +2184,14 @@ var EXT_TO_LANG = {
|
|
|
1988
2184
|
".tfvars": "terraform"
|
|
1989
2185
|
};
|
|
1990
2186
|
// src/tools/lsp/config.ts
|
|
1991
|
-
import { existsSync as
|
|
1992
|
-
import { join as
|
|
2187
|
+
import { existsSync as existsSync7, readFileSync as readFileSync4 } from "fs";
|
|
2188
|
+
import { join as join8 } from "path";
|
|
1993
2189
|
import { homedir as homedir2 } from "os";
|
|
1994
2190
|
function loadJsonFile(path2) {
|
|
1995
|
-
if (!
|
|
2191
|
+
if (!existsSync7(path2))
|
|
1996
2192
|
return null;
|
|
1997
2193
|
try {
|
|
1998
|
-
return JSON.parse(
|
|
2194
|
+
return JSON.parse(readFileSync4(path2, "utf-8"));
|
|
1999
2195
|
} catch {
|
|
2000
2196
|
return null;
|
|
2001
2197
|
}
|
|
@@ -2003,9 +2199,9 @@ function loadJsonFile(path2) {
|
|
|
2003
2199
|
function getConfigPaths() {
|
|
2004
2200
|
const cwd = process.cwd();
|
|
2005
2201
|
return {
|
|
2006
|
-
project:
|
|
2007
|
-
user:
|
|
2008
|
-
opencode:
|
|
2202
|
+
project: join8(cwd, ".opencode", "oh-my-opencode.json"),
|
|
2203
|
+
user: join8(homedir2(), ".config", "opencode", "oh-my-opencode.json"),
|
|
2204
|
+
opencode: join8(homedir2(), ".config", "opencode", "opencode.json")
|
|
2009
2205
|
};
|
|
2010
2206
|
}
|
|
2011
2207
|
function loadAllConfigs() {
|
|
@@ -2098,7 +2294,7 @@ function isServerInstalled(command) {
|
|
|
2098
2294
|
const pathEnv = process.env.PATH || "";
|
|
2099
2295
|
const paths = pathEnv.split(":");
|
|
2100
2296
|
for (const p of paths) {
|
|
2101
|
-
if (
|
|
2297
|
+
if (existsSync7(join8(p, cmd))) {
|
|
2102
2298
|
return true;
|
|
2103
2299
|
}
|
|
2104
2300
|
}
|
|
@@ -2148,8 +2344,8 @@ function getAllServers() {
|
|
|
2148
2344
|
}
|
|
2149
2345
|
// src/tools/lsp/client.ts
|
|
2150
2346
|
var {spawn: spawn3 } = globalThis.Bun;
|
|
2151
|
-
import { readFileSync as
|
|
2152
|
-
import { extname, resolve } from "path";
|
|
2347
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
2348
|
+
import { extname, resolve as resolve2 } from "path";
|
|
2153
2349
|
class LSPServerManager {
|
|
2154
2350
|
static instance;
|
|
2155
2351
|
clients = new Map;
|
|
@@ -2299,7 +2495,7 @@ class LSPClient {
|
|
|
2299
2495
|
}
|
|
2300
2496
|
this.startReading();
|
|
2301
2497
|
this.startStderrReading();
|
|
2302
|
-
await new Promise((
|
|
2498
|
+
await new Promise((resolve3) => setTimeout(resolve3, 100));
|
|
2303
2499
|
if (this.proc.exitCode !== null) {
|
|
2304
2500
|
const stderr = this.stderrBuffer.join(`
|
|
2305
2501
|
`);
|
|
@@ -2436,8 +2632,8 @@ stderr: ${stderr}` : ""));
|
|
|
2436
2632
|
\r
|
|
2437
2633
|
`;
|
|
2438
2634
|
this.proc.stdin.write(header + msg);
|
|
2439
|
-
return new Promise((
|
|
2440
|
-
this.pending.set(id, { resolve:
|
|
2635
|
+
return new Promise((resolve3, reject) => {
|
|
2636
|
+
this.pending.set(id, { resolve: resolve3, reject });
|
|
2441
2637
|
setTimeout(() => {
|
|
2442
2638
|
if (this.pending.has(id)) {
|
|
2443
2639
|
this.pending.delete(id);
|
|
@@ -2545,10 +2741,10 @@ ${msg}`);
|
|
|
2545
2741
|
await new Promise((r) => setTimeout(r, 300));
|
|
2546
2742
|
}
|
|
2547
2743
|
async openFile(filePath) {
|
|
2548
|
-
const absPath =
|
|
2744
|
+
const absPath = resolve2(filePath);
|
|
2549
2745
|
if (this.openedFiles.has(absPath))
|
|
2550
2746
|
return;
|
|
2551
|
-
const text =
|
|
2747
|
+
const text = readFileSync5(absPath, "utf-8");
|
|
2552
2748
|
const ext = extname(absPath);
|
|
2553
2749
|
const languageId = getLanguageId(ext);
|
|
2554
2750
|
this.notify("textDocument/didOpen", {
|
|
@@ -2563,7 +2759,7 @@ ${msg}`);
|
|
|
2563
2759
|
await new Promise((r) => setTimeout(r, 1000));
|
|
2564
2760
|
}
|
|
2565
2761
|
async hover(filePath, line, character) {
|
|
2566
|
-
const absPath =
|
|
2762
|
+
const absPath = resolve2(filePath);
|
|
2567
2763
|
await this.openFile(absPath);
|
|
2568
2764
|
return this.send("textDocument/hover", {
|
|
2569
2765
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -2571,7 +2767,7 @@ ${msg}`);
|
|
|
2571
2767
|
});
|
|
2572
2768
|
}
|
|
2573
2769
|
async definition(filePath, line, character) {
|
|
2574
|
-
const absPath =
|
|
2770
|
+
const absPath = resolve2(filePath);
|
|
2575
2771
|
await this.openFile(absPath);
|
|
2576
2772
|
return this.send("textDocument/definition", {
|
|
2577
2773
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -2579,7 +2775,7 @@ ${msg}`);
|
|
|
2579
2775
|
});
|
|
2580
2776
|
}
|
|
2581
2777
|
async references(filePath, line, character, includeDeclaration = true) {
|
|
2582
|
-
const absPath =
|
|
2778
|
+
const absPath = resolve2(filePath);
|
|
2583
2779
|
await this.openFile(absPath);
|
|
2584
2780
|
return this.send("textDocument/references", {
|
|
2585
2781
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -2588,7 +2784,7 @@ ${msg}`);
|
|
|
2588
2784
|
});
|
|
2589
2785
|
}
|
|
2590
2786
|
async documentSymbols(filePath) {
|
|
2591
|
-
const absPath =
|
|
2787
|
+
const absPath = resolve2(filePath);
|
|
2592
2788
|
await this.openFile(absPath);
|
|
2593
2789
|
return this.send("textDocument/documentSymbol", {
|
|
2594
2790
|
textDocument: { uri: `file://${absPath}` }
|
|
@@ -2598,7 +2794,7 @@ ${msg}`);
|
|
|
2598
2794
|
return this.send("workspace/symbol", { query });
|
|
2599
2795
|
}
|
|
2600
2796
|
async diagnostics(filePath) {
|
|
2601
|
-
const absPath =
|
|
2797
|
+
const absPath = resolve2(filePath);
|
|
2602
2798
|
const uri = `file://${absPath}`;
|
|
2603
2799
|
await this.openFile(absPath);
|
|
2604
2800
|
await new Promise((r) => setTimeout(r, 500));
|
|
@@ -2613,7 +2809,7 @@ ${msg}`);
|
|
|
2613
2809
|
return { items: this.diagnosticsStore.get(uri) ?? [] };
|
|
2614
2810
|
}
|
|
2615
2811
|
async prepareRename(filePath, line, character) {
|
|
2616
|
-
const absPath =
|
|
2812
|
+
const absPath = resolve2(filePath);
|
|
2617
2813
|
await this.openFile(absPath);
|
|
2618
2814
|
return this.send("textDocument/prepareRename", {
|
|
2619
2815
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -2621,7 +2817,7 @@ ${msg}`);
|
|
|
2621
2817
|
});
|
|
2622
2818
|
}
|
|
2623
2819
|
async rename(filePath, line, character, newName) {
|
|
2624
|
-
const absPath =
|
|
2820
|
+
const absPath = resolve2(filePath);
|
|
2625
2821
|
await this.openFile(absPath);
|
|
2626
2822
|
return this.send("textDocument/rename", {
|
|
2627
2823
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -2630,7 +2826,7 @@ ${msg}`);
|
|
|
2630
2826
|
});
|
|
2631
2827
|
}
|
|
2632
2828
|
async codeAction(filePath, startLine, startChar, endLine, endChar, only) {
|
|
2633
|
-
const absPath =
|
|
2829
|
+
const absPath = resolve2(filePath);
|
|
2634
2830
|
await this.openFile(absPath);
|
|
2635
2831
|
return this.send("textDocument/codeAction", {
|
|
2636
2832
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -2662,26 +2858,26 @@ ${msg}`);
|
|
|
2662
2858
|
}
|
|
2663
2859
|
}
|
|
2664
2860
|
// src/tools/lsp/utils.ts
|
|
2665
|
-
import { extname as extname2, resolve as
|
|
2666
|
-
import { existsSync as
|
|
2861
|
+
import { extname as extname2, resolve as resolve3 } from "path";
|
|
2862
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
2667
2863
|
function findWorkspaceRoot(filePath) {
|
|
2668
|
-
let dir =
|
|
2669
|
-
if (!
|
|
2864
|
+
let dir = resolve3(filePath);
|
|
2865
|
+
if (!existsSync8(dir) || !__require("fs").statSync(dir).isDirectory()) {
|
|
2670
2866
|
dir = __require("path").dirname(dir);
|
|
2671
2867
|
}
|
|
2672
2868
|
const markers = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
|
|
2673
2869
|
while (dir !== "/") {
|
|
2674
2870
|
for (const marker of markers) {
|
|
2675
|
-
if (
|
|
2871
|
+
if (existsSync8(__require("path").join(dir, marker))) {
|
|
2676
2872
|
return dir;
|
|
2677
2873
|
}
|
|
2678
2874
|
}
|
|
2679
2875
|
dir = __require("path").dirname(dir);
|
|
2680
2876
|
}
|
|
2681
|
-
return __require("path").dirname(
|
|
2877
|
+
return __require("path").dirname(resolve3(filePath));
|
|
2682
2878
|
}
|
|
2683
2879
|
async function withLspClient(filePath, fn) {
|
|
2684
|
-
const absPath =
|
|
2880
|
+
const absPath = resolve3(filePath);
|
|
2685
2881
|
const ext = extname2(absPath);
|
|
2686
2882
|
const server = findServerForExtension(ext);
|
|
2687
2883
|
if (!server) {
|
|
@@ -2830,7 +3026,7 @@ function formatCodeActions(actions) {
|
|
|
2830
3026
|
}
|
|
2831
3027
|
function applyTextEditsToFile(filePath, edits) {
|
|
2832
3028
|
try {
|
|
2833
|
-
let content =
|
|
3029
|
+
let content = readFileSync6(filePath, "utf-8");
|
|
2834
3030
|
const lines = content.split(`
|
|
2835
3031
|
`);
|
|
2836
3032
|
const sortedEdits = [...edits].sort((a, b) => {
|
|
@@ -2855,7 +3051,7 @@ function applyTextEditsToFile(filePath, edits) {
|
|
|
2855
3051
|
`));
|
|
2856
3052
|
}
|
|
2857
3053
|
}
|
|
2858
|
-
|
|
3054
|
+
writeFileSync3(filePath, lines.join(`
|
|
2859
3055
|
`), "utf-8");
|
|
2860
3056
|
return { success: true, editCount: edits.length };
|
|
2861
3057
|
} catch (err) {
|
|
@@ -2886,7 +3082,7 @@ function applyWorkspaceEdit(edit) {
|
|
|
2886
3082
|
if (change.kind === "create") {
|
|
2887
3083
|
try {
|
|
2888
3084
|
const filePath = change.uri.replace("file://", "");
|
|
2889
|
-
|
|
3085
|
+
writeFileSync3(filePath, "", "utf-8");
|
|
2890
3086
|
result.filesModified.push(filePath);
|
|
2891
3087
|
} catch (err) {
|
|
2892
3088
|
result.success = false;
|
|
@@ -2896,8 +3092,8 @@ function applyWorkspaceEdit(edit) {
|
|
|
2896
3092
|
try {
|
|
2897
3093
|
const oldPath = change.oldUri.replace("file://", "");
|
|
2898
3094
|
const newPath = change.newUri.replace("file://", "");
|
|
2899
|
-
const content =
|
|
2900
|
-
|
|
3095
|
+
const content = readFileSync6(oldPath, "utf-8");
|
|
3096
|
+
writeFileSync3(newPath, content, "utf-8");
|
|
2901
3097
|
__require("fs").unlinkSync(oldPath);
|
|
2902
3098
|
result.filesModified.push(newPath);
|
|
2903
3099
|
} catch (err) {
|
|
@@ -15597,13 +15793,13 @@ var lsp_code_action_resolve = tool({
|
|
|
15597
15793
|
});
|
|
15598
15794
|
// src/tools/ast-grep/constants.ts
|
|
15599
15795
|
import { createRequire as createRequire4 } from "module";
|
|
15600
|
-
import { dirname as
|
|
15601
|
-
import { existsSync as
|
|
15796
|
+
import { dirname as dirname3, join as join10 } from "path";
|
|
15797
|
+
import { existsSync as existsSync10, statSync } from "fs";
|
|
15602
15798
|
|
|
15603
15799
|
// src/tools/ast-grep/downloader.ts
|
|
15604
15800
|
var {spawn: spawn4 } = globalThis.Bun;
|
|
15605
|
-
import { existsSync as
|
|
15606
|
-
import { join as
|
|
15801
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, chmodSync as chmodSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
15802
|
+
import { join as join9 } from "path";
|
|
15607
15803
|
import { homedir as homedir3 } from "os";
|
|
15608
15804
|
import { createRequire as createRequire3 } from "module";
|
|
15609
15805
|
var REPO2 = "ast-grep/ast-grep";
|
|
@@ -15629,19 +15825,19 @@ var PLATFORM_MAP2 = {
|
|
|
15629
15825
|
function getCacheDir2() {
|
|
15630
15826
|
if (process.platform === "win32") {
|
|
15631
15827
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
15632
|
-
const base2 = localAppData ||
|
|
15633
|
-
return
|
|
15828
|
+
const base2 = localAppData || join9(homedir3(), "AppData", "Local");
|
|
15829
|
+
return join9(base2, "oh-my-opencode", "bin");
|
|
15634
15830
|
}
|
|
15635
15831
|
const xdgCache2 = process.env.XDG_CACHE_HOME;
|
|
15636
|
-
const base = xdgCache2 ||
|
|
15637
|
-
return
|
|
15832
|
+
const base = xdgCache2 || join9(homedir3(), ".cache");
|
|
15833
|
+
return join9(base, "oh-my-opencode", "bin");
|
|
15638
15834
|
}
|
|
15639
15835
|
function getBinaryName3() {
|
|
15640
15836
|
return process.platform === "win32" ? "sg.exe" : "sg";
|
|
15641
15837
|
}
|
|
15642
15838
|
function getCachedBinaryPath2() {
|
|
15643
|
-
const binaryPath =
|
|
15644
|
-
return
|
|
15839
|
+
const binaryPath = join9(getCacheDir2(), getBinaryName3());
|
|
15840
|
+
return existsSync9(binaryPath) ? binaryPath : null;
|
|
15645
15841
|
}
|
|
15646
15842
|
async function extractZip2(archivePath, destDir) {
|
|
15647
15843
|
const proc = process.platform === "win32" ? spawn4([
|
|
@@ -15667,8 +15863,8 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
|
|
|
15667
15863
|
}
|
|
15668
15864
|
const cacheDir = getCacheDir2();
|
|
15669
15865
|
const binaryName = getBinaryName3();
|
|
15670
|
-
const binaryPath =
|
|
15671
|
-
if (
|
|
15866
|
+
const binaryPath = join9(cacheDir, binaryName);
|
|
15867
|
+
if (existsSync9(binaryPath)) {
|
|
15672
15868
|
return binaryPath;
|
|
15673
15869
|
}
|
|
15674
15870
|
const { arch, os: os2 } = platformInfo;
|
|
@@ -15676,21 +15872,21 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
|
|
|
15676
15872
|
const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
|
|
15677
15873
|
console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
|
|
15678
15874
|
try {
|
|
15679
|
-
if (!
|
|
15680
|
-
|
|
15875
|
+
if (!existsSync9(cacheDir)) {
|
|
15876
|
+
mkdirSync4(cacheDir, { recursive: true });
|
|
15681
15877
|
}
|
|
15682
15878
|
const response = await fetch(downloadUrl, { redirect: "follow" });
|
|
15683
15879
|
if (!response.ok) {
|
|
15684
15880
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
15685
15881
|
}
|
|
15686
|
-
const archivePath =
|
|
15882
|
+
const archivePath = join9(cacheDir, assetName);
|
|
15687
15883
|
const arrayBuffer = await response.arrayBuffer();
|
|
15688
15884
|
await Bun.write(archivePath, arrayBuffer);
|
|
15689
15885
|
await extractZip2(archivePath, cacheDir);
|
|
15690
|
-
if (
|
|
15691
|
-
|
|
15886
|
+
if (existsSync9(archivePath)) {
|
|
15887
|
+
unlinkSync4(archivePath);
|
|
15692
15888
|
}
|
|
15693
|
-
if (process.platform !== "win32" &&
|
|
15889
|
+
if (process.platform !== "win32" && existsSync9(binaryPath)) {
|
|
15694
15890
|
chmodSync2(binaryPath, 493);
|
|
15695
15891
|
}
|
|
15696
15892
|
console.log(`[oh-my-opencode] ast-grep binary ready.`);
|
|
@@ -15740,9 +15936,9 @@ function findSgCliPathSync() {
|
|
|
15740
15936
|
try {
|
|
15741
15937
|
const require2 = createRequire4(import.meta.url);
|
|
15742
15938
|
const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
|
|
15743
|
-
const cliDir =
|
|
15744
|
-
const sgPath =
|
|
15745
|
-
if (
|
|
15939
|
+
const cliDir = dirname3(cliPkgPath);
|
|
15940
|
+
const sgPath = join10(cliDir, binaryName);
|
|
15941
|
+
if (existsSync10(sgPath) && isValidBinary(sgPath)) {
|
|
15746
15942
|
return sgPath;
|
|
15747
15943
|
}
|
|
15748
15944
|
} catch {}
|
|
@@ -15751,10 +15947,10 @@ function findSgCliPathSync() {
|
|
|
15751
15947
|
try {
|
|
15752
15948
|
const require2 = createRequire4(import.meta.url);
|
|
15753
15949
|
const pkgPath = require2.resolve(`${platformPkg}/package.json`);
|
|
15754
|
-
const pkgDir =
|
|
15950
|
+
const pkgDir = dirname3(pkgPath);
|
|
15755
15951
|
const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
15756
|
-
const binaryPath =
|
|
15757
|
-
if (
|
|
15952
|
+
const binaryPath = join10(pkgDir, astGrepName);
|
|
15953
|
+
if (existsSync10(binaryPath) && isValidBinary(binaryPath)) {
|
|
15758
15954
|
return binaryPath;
|
|
15759
15955
|
}
|
|
15760
15956
|
} catch {}
|
|
@@ -15762,7 +15958,7 @@ function findSgCliPathSync() {
|
|
|
15762
15958
|
if (process.platform === "darwin") {
|
|
15763
15959
|
const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
|
|
15764
15960
|
for (const path2 of homebrewPaths) {
|
|
15765
|
-
if (
|
|
15961
|
+
if (existsSync10(path2) && isValidBinary(path2)) {
|
|
15766
15962
|
return path2;
|
|
15767
15963
|
}
|
|
15768
15964
|
}
|
|
@@ -15846,11 +16042,11 @@ var LANG_EXTENSIONS = {
|
|
|
15846
16042
|
|
|
15847
16043
|
// src/tools/ast-grep/cli.ts
|
|
15848
16044
|
var {spawn: spawn5 } = globalThis.Bun;
|
|
15849
|
-
import { existsSync as
|
|
16045
|
+
import { existsSync as existsSync11 } from "fs";
|
|
15850
16046
|
var resolvedCliPath3 = null;
|
|
15851
16047
|
var initPromise2 = null;
|
|
15852
16048
|
async function getAstGrepPath() {
|
|
15853
|
-
if (resolvedCliPath3 !== null &&
|
|
16049
|
+
if (resolvedCliPath3 !== null && existsSync11(resolvedCliPath3)) {
|
|
15854
16050
|
return resolvedCliPath3;
|
|
15855
16051
|
}
|
|
15856
16052
|
if (initPromise2) {
|
|
@@ -15858,7 +16054,7 @@ async function getAstGrepPath() {
|
|
|
15858
16054
|
}
|
|
15859
16055
|
initPromise2 = (async () => {
|
|
15860
16056
|
const syncPath = findSgCliPathSync();
|
|
15861
|
-
if (syncPath &&
|
|
16057
|
+
if (syncPath && existsSync11(syncPath)) {
|
|
15862
16058
|
resolvedCliPath3 = syncPath;
|
|
15863
16059
|
setSgCliPath(syncPath);
|
|
15864
16060
|
return syncPath;
|
|
@@ -15892,7 +16088,7 @@ async function runSg(options) {
|
|
|
15892
16088
|
const paths = options.paths && options.paths.length > 0 ? options.paths : ["."];
|
|
15893
16089
|
args.push(...paths);
|
|
15894
16090
|
let cliPath = getSgCliPath();
|
|
15895
|
-
if (!
|
|
16091
|
+
if (!existsSync11(cliPath) && cliPath !== "sg") {
|
|
15896
16092
|
const downloadedPath = await getAstGrepPath();
|
|
15897
16093
|
if (downloadedPath) {
|
|
15898
16094
|
cliPath = downloadedPath;
|
|
@@ -16333,12 +16529,12 @@ var ast_grep_transform = tool({
|
|
|
16333
16529
|
}
|
|
16334
16530
|
}
|
|
16335
16531
|
});
|
|
16336
|
-
// src/tools/
|
|
16532
|
+
// src/tools/grep/cli.ts
|
|
16337
16533
|
var {spawn: spawn6 } = globalThis.Bun;
|
|
16338
16534
|
|
|
16339
|
-
// src/tools/
|
|
16340
|
-
import { existsSync as
|
|
16341
|
-
import { join as
|
|
16535
|
+
// src/tools/grep/constants.ts
|
|
16536
|
+
import { existsSync as existsSync12 } from "fs";
|
|
16537
|
+
import { join as join11, dirname as dirname4 } from "path";
|
|
16342
16538
|
import { spawnSync } from "child_process";
|
|
16343
16539
|
var cachedCli = null;
|
|
16344
16540
|
function findExecutable(name) {
|
|
@@ -16355,17 +16551,17 @@ function findExecutable(name) {
|
|
|
16355
16551
|
}
|
|
16356
16552
|
function getOpenCodeBundledRg() {
|
|
16357
16553
|
const execPath = process.execPath;
|
|
16358
|
-
const execDir =
|
|
16554
|
+
const execDir = dirname4(execPath);
|
|
16359
16555
|
const isWindows = process.platform === "win32";
|
|
16360
16556
|
const rgName = isWindows ? "rg.exe" : "rg";
|
|
16361
16557
|
const candidates = [
|
|
16362
|
-
|
|
16363
|
-
|
|
16364
|
-
|
|
16365
|
-
|
|
16558
|
+
join11(execDir, rgName),
|
|
16559
|
+
join11(execDir, "bin", rgName),
|
|
16560
|
+
join11(execDir, "..", "bin", rgName),
|
|
16561
|
+
join11(execDir, "..", "libexec", rgName)
|
|
16366
16562
|
];
|
|
16367
16563
|
for (const candidate of candidates) {
|
|
16368
|
-
if (
|
|
16564
|
+
if (existsSync12(candidate)) {
|
|
16369
16565
|
return candidate;
|
|
16370
16566
|
}
|
|
16371
16567
|
}
|
|
@@ -16407,7 +16603,7 @@ var RG_SAFETY_FLAGS = [
|
|
|
16407
16603
|
];
|
|
16408
16604
|
var GREP_SAFETY_FLAGS = ["-n", "-H", "--color=never"];
|
|
16409
16605
|
|
|
16410
|
-
// src/tools/
|
|
16606
|
+
// src/tools/grep/cli.ts
|
|
16411
16607
|
function buildRgArgs(options) {
|
|
16412
16608
|
const args = [
|
|
16413
16609
|
...RG_SAFETY_FLAGS,
|
|
@@ -16551,7 +16747,7 @@ async function runRg(options) {
|
|
|
16551
16747
|
}
|
|
16552
16748
|
}
|
|
16553
16749
|
|
|
16554
|
-
// src/tools/
|
|
16750
|
+
// src/tools/grep/utils.ts
|
|
16555
16751
|
function formatGrepResult(result) {
|
|
16556
16752
|
if (result.error) {
|
|
16557
16753
|
return `Error: ${result.error}`;
|
|
@@ -16582,8 +16778,8 @@ function formatGrepResult(result) {
|
|
|
16582
16778
|
`);
|
|
16583
16779
|
}
|
|
16584
16780
|
|
|
16585
|
-
// src/tools/
|
|
16586
|
-
var
|
|
16781
|
+
// src/tools/grep/tools.ts
|
|
16782
|
+
var grep = tool({
|
|
16587
16783
|
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
16784
|
args: {
|
|
16589
16785
|
pattern: tool.schema.string().describe("The regex pattern to search for in file contents"),
|
|
@@ -16607,6 +16803,162 @@ var safe_grep = tool({
|
|
|
16607
16803
|
}
|
|
16608
16804
|
});
|
|
16609
16805
|
|
|
16806
|
+
// src/tools/glob/cli.ts
|
|
16807
|
+
var {spawn: spawn7 } = globalThis.Bun;
|
|
16808
|
+
|
|
16809
|
+
// src/tools/glob/constants.ts
|
|
16810
|
+
var DEFAULT_TIMEOUT_MS3 = 60000;
|
|
16811
|
+
var DEFAULT_LIMIT = 100;
|
|
16812
|
+
var DEFAULT_MAX_DEPTH2 = 20;
|
|
16813
|
+
var DEFAULT_MAX_OUTPUT_BYTES3 = 10 * 1024 * 1024;
|
|
16814
|
+
var RG_FILES_FLAGS = [
|
|
16815
|
+
"--files",
|
|
16816
|
+
"--color=never",
|
|
16817
|
+
"--glob=!.git/*"
|
|
16818
|
+
];
|
|
16819
|
+
|
|
16820
|
+
// src/tools/glob/cli.ts
|
|
16821
|
+
import { stat } from "fs/promises";
|
|
16822
|
+
function buildRgArgs2(options) {
|
|
16823
|
+
const args = [
|
|
16824
|
+
...RG_FILES_FLAGS,
|
|
16825
|
+
`--max-depth=${Math.min(options.maxDepth ?? DEFAULT_MAX_DEPTH2, DEFAULT_MAX_DEPTH2)}`
|
|
16826
|
+
];
|
|
16827
|
+
if (options.hidden)
|
|
16828
|
+
args.push("--hidden");
|
|
16829
|
+
if (options.noIgnore)
|
|
16830
|
+
args.push("--no-ignore");
|
|
16831
|
+
args.push(`--glob=${options.pattern}`);
|
|
16832
|
+
return args;
|
|
16833
|
+
}
|
|
16834
|
+
function buildFindArgs(options) {
|
|
16835
|
+
const args = ["."];
|
|
16836
|
+
const maxDepth = Math.min(options.maxDepth ?? DEFAULT_MAX_DEPTH2, DEFAULT_MAX_DEPTH2);
|
|
16837
|
+
args.push("-maxdepth", String(maxDepth));
|
|
16838
|
+
args.push("-type", "f");
|
|
16839
|
+
args.push("-name", options.pattern);
|
|
16840
|
+
if (!options.hidden) {
|
|
16841
|
+
args.push("-not", "-path", "*/.*");
|
|
16842
|
+
}
|
|
16843
|
+
return args;
|
|
16844
|
+
}
|
|
16845
|
+
async function getFileMtime(filePath) {
|
|
16846
|
+
try {
|
|
16847
|
+
const stats = await stat(filePath);
|
|
16848
|
+
return stats.mtime.getTime();
|
|
16849
|
+
} catch {
|
|
16850
|
+
return 0;
|
|
16851
|
+
}
|
|
16852
|
+
}
|
|
16853
|
+
async function runRgFiles(options) {
|
|
16854
|
+
const cli = resolveGrepCli();
|
|
16855
|
+
const timeout = Math.min(options.timeout ?? DEFAULT_TIMEOUT_MS3, DEFAULT_TIMEOUT_MS3);
|
|
16856
|
+
const limit = Math.min(options.limit ?? DEFAULT_LIMIT, DEFAULT_LIMIT);
|
|
16857
|
+
const isRg = cli.backend === "rg";
|
|
16858
|
+
const args = isRg ? buildRgArgs2(options) : buildFindArgs(options);
|
|
16859
|
+
const paths = options.paths?.length ? options.paths : ["."];
|
|
16860
|
+
if (isRg) {
|
|
16861
|
+
args.push(...paths);
|
|
16862
|
+
}
|
|
16863
|
+
const cwd = paths[0] || ".";
|
|
16864
|
+
const proc = spawn7([cli.path, ...args], {
|
|
16865
|
+
stdout: "pipe",
|
|
16866
|
+
stderr: "pipe",
|
|
16867
|
+
cwd: isRg ? undefined : cwd
|
|
16868
|
+
});
|
|
16869
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
16870
|
+
const id = setTimeout(() => {
|
|
16871
|
+
proc.kill();
|
|
16872
|
+
reject(new Error(`Glob search timeout after ${timeout}ms`));
|
|
16873
|
+
}, timeout);
|
|
16874
|
+
proc.exited.then(() => clearTimeout(id));
|
|
16875
|
+
});
|
|
16876
|
+
try {
|
|
16877
|
+
const stdout = await Promise.race([new Response(proc.stdout).text(), timeoutPromise]);
|
|
16878
|
+
const stderr = await new Response(proc.stderr).text();
|
|
16879
|
+
const exitCode = await proc.exited;
|
|
16880
|
+
if (exitCode > 1 && stderr.trim()) {
|
|
16881
|
+
return {
|
|
16882
|
+
files: [],
|
|
16883
|
+
totalFiles: 0,
|
|
16884
|
+
truncated: false,
|
|
16885
|
+
error: stderr.trim()
|
|
16886
|
+
};
|
|
16887
|
+
}
|
|
16888
|
+
const truncatedOutput = stdout.length >= DEFAULT_MAX_OUTPUT_BYTES3;
|
|
16889
|
+
const outputToProcess = truncatedOutput ? stdout.substring(0, DEFAULT_MAX_OUTPUT_BYTES3) : stdout;
|
|
16890
|
+
const lines = outputToProcess.trim().split(`
|
|
16891
|
+
`).filter(Boolean);
|
|
16892
|
+
const files = [];
|
|
16893
|
+
let truncated = false;
|
|
16894
|
+
for (const line of lines) {
|
|
16895
|
+
if (files.length >= limit) {
|
|
16896
|
+
truncated = true;
|
|
16897
|
+
break;
|
|
16898
|
+
}
|
|
16899
|
+
const filePath = isRg ? line : `${cwd}/${line}`;
|
|
16900
|
+
const mtime = await getFileMtime(filePath);
|
|
16901
|
+
files.push({ path: filePath, mtime });
|
|
16902
|
+
}
|
|
16903
|
+
files.sort((a, b) => b.mtime - a.mtime);
|
|
16904
|
+
return {
|
|
16905
|
+
files,
|
|
16906
|
+
totalFiles: files.length,
|
|
16907
|
+
truncated: truncated || truncatedOutput
|
|
16908
|
+
};
|
|
16909
|
+
} catch (e) {
|
|
16910
|
+
return {
|
|
16911
|
+
files: [],
|
|
16912
|
+
totalFiles: 0,
|
|
16913
|
+
truncated: false,
|
|
16914
|
+
error: e instanceof Error ? e.message : String(e)
|
|
16915
|
+
};
|
|
16916
|
+
}
|
|
16917
|
+
}
|
|
16918
|
+
|
|
16919
|
+
// src/tools/glob/utils.ts
|
|
16920
|
+
function formatGlobResult(result) {
|
|
16921
|
+
if (result.error) {
|
|
16922
|
+
return `Error: ${result.error}`;
|
|
16923
|
+
}
|
|
16924
|
+
if (result.files.length === 0) {
|
|
16925
|
+
return "No files found";
|
|
16926
|
+
}
|
|
16927
|
+
const lines = [];
|
|
16928
|
+
lines.push(`Found ${result.totalFiles} file(s)`);
|
|
16929
|
+
lines.push("");
|
|
16930
|
+
for (const file2 of result.files) {
|
|
16931
|
+
lines.push(file2.path);
|
|
16932
|
+
}
|
|
16933
|
+
if (result.truncated) {
|
|
16934
|
+
lines.push("");
|
|
16935
|
+
lines.push("(Results are truncated. Consider using a more specific path or pattern.)");
|
|
16936
|
+
}
|
|
16937
|
+
return lines.join(`
|
|
16938
|
+
`);
|
|
16939
|
+
}
|
|
16940
|
+
|
|
16941
|
+
// src/tools/glob/tools.ts
|
|
16942
|
+
var glob = tool({
|
|
16943
|
+
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.",
|
|
16944
|
+
args: {
|
|
16945
|
+
pattern: tool.schema.string().describe("The glob pattern to match files against"),
|
|
16946
|
+
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.")
|
|
16947
|
+
},
|
|
16948
|
+
execute: async (args) => {
|
|
16949
|
+
try {
|
|
16950
|
+
const paths = args.path ? [args.path] : undefined;
|
|
16951
|
+
const result = await runRgFiles({
|
|
16952
|
+
pattern: args.pattern,
|
|
16953
|
+
paths
|
|
16954
|
+
});
|
|
16955
|
+
return formatGlobResult(result);
|
|
16956
|
+
} catch (e) {
|
|
16957
|
+
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
16958
|
+
}
|
|
16959
|
+
}
|
|
16960
|
+
});
|
|
16961
|
+
|
|
16610
16962
|
// src/tools/index.ts
|
|
16611
16963
|
var builtinTools = {
|
|
16612
16964
|
lsp_hover,
|
|
@@ -16622,7 +16974,8 @@ var builtinTools = {
|
|
|
16622
16974
|
lsp_code_action_resolve,
|
|
16623
16975
|
ast_grep_search,
|
|
16624
16976
|
ast_grep_replace,
|
|
16625
|
-
|
|
16977
|
+
grep,
|
|
16978
|
+
glob
|
|
16626
16979
|
};
|
|
16627
16980
|
|
|
16628
16981
|
// src/mcp/websearch-exa.ts
|
|
@@ -16730,6 +17083,8 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
16730
17083
|
const pulseMonitor = createPulseMonitorHook(ctx);
|
|
16731
17084
|
const commentChecker = createCommentCheckerHooks();
|
|
16732
17085
|
const grepOutputTruncator = createGrepOutputTruncatorHook(ctx);
|
|
17086
|
+
const directoryAgentsInjector = createDirectoryAgentsInjectorHook(ctx);
|
|
17087
|
+
const emptyTaskResponseDetector = createEmptyTaskResponseDetectorHook(ctx);
|
|
16733
17088
|
updateTerminalTitle({ sessionId: "main" });
|
|
16734
17089
|
const pluginConfig = loadPluginConfig(ctx.directory);
|
|
16735
17090
|
let mainSessionID;
|
|
@@ -16744,8 +17099,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
16744
17099
|
...agents
|
|
16745
17100
|
};
|
|
16746
17101
|
config3.tools = {
|
|
16747
|
-
...config3.tools
|
|
16748
|
-
grep: false
|
|
17102
|
+
...config3.tools
|
|
16749
17103
|
};
|
|
16750
17104
|
config3.mcp = {
|
|
16751
17105
|
...config3.mcp,
|
|
@@ -16756,6 +17110,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
16756
17110
|
await todoContinuationEnforcer(input);
|
|
16757
17111
|
await contextWindowMonitor.event(input);
|
|
16758
17112
|
await pulseMonitor.event(input);
|
|
17113
|
+
await directoryAgentsInjector.event(input);
|
|
16759
17114
|
const { event } = input;
|
|
16760
17115
|
const props = event.properties;
|
|
16761
17116
|
if (event.type === "session.created") {
|
|
@@ -16855,6 +17210,8 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
16855
17210
|
await grepOutputTruncator["tool.execute.after"](input, output);
|
|
16856
17211
|
await contextWindowMonitor["tool.execute.after"](input, output);
|
|
16857
17212
|
await commentChecker["tool.execute.after"](input, output);
|
|
17213
|
+
await directoryAgentsInjector["tool.execute.after"](input, output);
|
|
17214
|
+
await emptyTaskResponseDetector["tool.execute.after"](input, output);
|
|
16858
17215
|
if (input.sessionID === mainSessionID) {
|
|
16859
17216
|
updateTerminalTitle({
|
|
16860
17217
|
sessionId: input.sessionID,
|