oh-my-opencode 0.1.25 → 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 +21 -5
- package/README.md +21 -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/grep-output-truncator.d.ts +12 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/pulse-monitor.d.ts +10 -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 +892 -297
- package/dist/tools/ast-grep/index.d.ts +8 -8
- package/dist/tools/ast-grep/tools.d.ts +12 -12
- 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 +20 -9
- package/dist/tools/lsp/client.d.ts +5 -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
|
@@ -602,18 +602,13 @@ function createBuiltinAgents(disabledAgents = [], agentOverrides = {}) {
|
|
|
602
602
|
return result;
|
|
603
603
|
}
|
|
604
604
|
// src/hooks/todo-continuation-enforcer.ts
|
|
605
|
-
var CONTINUATION_PROMPT = `[SYSTEM REMINDER - TODO
|
|
605
|
+
var CONTINUATION_PROMPT = `[SYSTEM REMINDER - TODO CONTINUATION]
|
|
606
606
|
|
|
607
|
-
|
|
607
|
+
Incomplete tasks remain in your todo list. Continue working on the next pending task.
|
|
608
608
|
|
|
609
|
-
|
|
610
|
-
-
|
|
611
|
-
-
|
|
612
|
-
- Work honestly and diligently to finish every task
|
|
613
|
-
- Do NOT ask for permission to continue - just proceed with the work
|
|
614
|
-
- Mark each task as completed as soon as you finish it
|
|
615
|
-
|
|
616
|
-
Resume your work NOW.`;
|
|
609
|
+
- Proceed without asking for permission
|
|
610
|
+
- Mark each task complete when finished
|
|
611
|
+
- Do not stop until all tasks are done`;
|
|
617
612
|
function detectInterrupt(error) {
|
|
618
613
|
if (!error)
|
|
619
614
|
return false;
|
|
@@ -694,7 +689,7 @@ function createTodoContinuationEnforcer(ctx) {
|
|
|
694
689
|
type: "text",
|
|
695
690
|
text: `${CONTINUATION_PROMPT}
|
|
696
691
|
|
|
697
|
-
[Status: ${incomplete.length}/${todos.length}
|
|
692
|
+
[Status: ${todos.length - incomplete.length}/${todos.length} completed, ${incomplete.length} remaining]`
|
|
698
693
|
}
|
|
699
694
|
]
|
|
700
695
|
},
|
|
@@ -789,8 +784,11 @@ ${CONTEXT_REMINDER}
|
|
|
789
784
|
event: eventHandler
|
|
790
785
|
};
|
|
791
786
|
}
|
|
792
|
-
// src/hooks/session-recovery.ts
|
|
793
|
-
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
|
|
794
792
|
import { join } from "path";
|
|
795
793
|
|
|
796
794
|
// node_modules/xdg-basedir/index.js
|
|
@@ -812,141 +810,36 @@ if (xdgConfig) {
|
|
|
812
810
|
xdgConfigDirectories.unshift(xdgConfig);
|
|
813
811
|
}
|
|
814
812
|
|
|
815
|
-
// src/hooks/session-recovery.ts
|
|
813
|
+
// src/hooks/session-recovery/constants.ts
|
|
816
814
|
var OPENCODE_STORAGE = join(xdgData ?? "", "opencode", "storage");
|
|
817
815
|
var MESSAGE_STORAGE = join(OPENCODE_STORAGE, "message");
|
|
818
816
|
var PART_STORAGE = join(OPENCODE_STORAGE, "part");
|
|
819
|
-
function getErrorMessage(error) {
|
|
820
|
-
if (!error)
|
|
821
|
-
return "";
|
|
822
|
-
if (typeof error === "string")
|
|
823
|
-
return error.toLowerCase();
|
|
824
|
-
const errorObj = error;
|
|
825
|
-
return (errorObj.data?.message || errorObj.message || "").toLowerCase();
|
|
826
|
-
}
|
|
827
|
-
function detectErrorType(error) {
|
|
828
|
-
const message = getErrorMessage(error);
|
|
829
|
-
if (message.includes("tool_use") && message.includes("tool_result")) {
|
|
830
|
-
return "tool_result_missing";
|
|
831
|
-
}
|
|
832
|
-
if (message.includes("thinking") && (message.includes("first block") || message.includes("must start with") || message.includes("preceeding"))) {
|
|
833
|
-
return "thinking_block_order";
|
|
834
|
-
}
|
|
835
|
-
if (message.includes("thinking is disabled") && message.includes("cannot contain")) {
|
|
836
|
-
return "thinking_disabled_violation";
|
|
837
|
-
}
|
|
838
|
-
if (message.includes("non-empty content") || message.includes("must have non-empty content")) {
|
|
839
|
-
return "empty_content_message";
|
|
840
|
-
}
|
|
841
|
-
return null;
|
|
842
|
-
}
|
|
843
|
-
function extractToolUseIds(parts) {
|
|
844
|
-
return parts.filter((p) => p.type === "tool_use" && !!p.id).map((p) => p.id);
|
|
845
|
-
}
|
|
846
|
-
async function recoverToolResultMissing(client, sessionID, failedAssistantMsg) {
|
|
847
|
-
const parts = failedAssistantMsg.parts || [];
|
|
848
|
-
const toolUseIds = extractToolUseIds(parts);
|
|
849
|
-
if (toolUseIds.length === 0) {
|
|
850
|
-
return false;
|
|
851
|
-
}
|
|
852
|
-
const toolResultParts = toolUseIds.map((id) => ({
|
|
853
|
-
type: "tool_result",
|
|
854
|
-
tool_use_id: id,
|
|
855
|
-
content: "Operation cancelled by user (ESC pressed)"
|
|
856
|
-
}));
|
|
857
|
-
try {
|
|
858
|
-
await client.session.prompt({
|
|
859
|
-
path: { id: sessionID },
|
|
860
|
-
body: { parts: toolResultParts }
|
|
861
|
-
});
|
|
862
|
-
return true;
|
|
863
|
-
} catch {
|
|
864
|
-
return false;
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
async function recoverThinkingBlockOrder(client, sessionID, failedAssistantMsg, directory) {
|
|
868
|
-
const messageID = failedAssistantMsg.info?.id;
|
|
869
|
-
if (!messageID) {
|
|
870
|
-
return false;
|
|
871
|
-
}
|
|
872
|
-
const existingParts = failedAssistantMsg.parts || [];
|
|
873
|
-
const patchedParts = [{ type: "thinking", thinking: "" }, ...existingParts];
|
|
874
|
-
try {
|
|
875
|
-
await client.message?.update?.({
|
|
876
|
-
path: { id: messageID },
|
|
877
|
-
body: { parts: patchedParts }
|
|
878
|
-
});
|
|
879
|
-
return true;
|
|
880
|
-
} catch {}
|
|
881
|
-
try {
|
|
882
|
-
await client.session.patch?.({
|
|
883
|
-
path: { id: sessionID },
|
|
884
|
-
body: {
|
|
885
|
-
messageID,
|
|
886
|
-
parts: patchedParts
|
|
887
|
-
}
|
|
888
|
-
});
|
|
889
|
-
return true;
|
|
890
|
-
} catch {}
|
|
891
|
-
return await fallbackRevertStrategy(client, sessionID, failedAssistantMsg, directory);
|
|
892
|
-
}
|
|
893
|
-
async function recoverThinkingDisabledViolation(client, sessionID, failedAssistantMsg) {
|
|
894
|
-
const messageID = failedAssistantMsg.info?.id;
|
|
895
|
-
if (!messageID) {
|
|
896
|
-
return false;
|
|
897
|
-
}
|
|
898
|
-
const existingParts = failedAssistantMsg.parts || [];
|
|
899
|
-
const strippedParts = existingParts.filter((p) => p.type !== "thinking" && p.type !== "redacted_thinking");
|
|
900
|
-
if (strippedParts.length === 0) {
|
|
901
|
-
return false;
|
|
902
|
-
}
|
|
903
|
-
try {
|
|
904
|
-
await client.message?.update?.({
|
|
905
|
-
path: { id: messageID },
|
|
906
|
-
body: { parts: strippedParts }
|
|
907
|
-
});
|
|
908
|
-
return true;
|
|
909
|
-
} catch {}
|
|
910
|
-
try {
|
|
911
|
-
await client.session.patch?.({
|
|
912
|
-
path: { id: sessionID },
|
|
913
|
-
body: {
|
|
914
|
-
messageID,
|
|
915
|
-
parts: strippedParts
|
|
916
|
-
}
|
|
917
|
-
});
|
|
918
|
-
return true;
|
|
919
|
-
} catch {}
|
|
920
|
-
return false;
|
|
921
|
-
}
|
|
922
817
|
var THINKING_TYPES = new Set(["thinking", "redacted_thinking", "reasoning"]);
|
|
923
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
|
|
924
822
|
function generatePartId() {
|
|
925
823
|
const timestamp = Date.now().toString(16);
|
|
926
824
|
const random = Math.random().toString(36).substring(2, 10);
|
|
927
825
|
return `prt_${timestamp}${random}`;
|
|
928
826
|
}
|
|
929
827
|
function getMessageDir(sessionID) {
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
return false;
|
|
936
|
-
}
|
|
937
|
-
});
|
|
938
|
-
if (projectHash) {
|
|
939
|
-
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;
|
|
940
833
|
}
|
|
941
834
|
for (const dir of readdirSync(MESSAGE_STORAGE)) {
|
|
942
|
-
const sessionPath =
|
|
835
|
+
const sessionPath = join2(MESSAGE_STORAGE, dir, sessionID);
|
|
943
836
|
if (existsSync(sessionPath)) {
|
|
944
837
|
return sessionPath;
|
|
945
838
|
}
|
|
946
839
|
}
|
|
947
840
|
return "";
|
|
948
841
|
}
|
|
949
|
-
function
|
|
842
|
+
function readMessages(sessionID) {
|
|
950
843
|
const messageDir = getMessageDir(sessionID);
|
|
951
844
|
if (!messageDir || !existsSync(messageDir))
|
|
952
845
|
return [];
|
|
@@ -955,7 +848,7 @@ function readMessagesFromStorage(sessionID) {
|
|
|
955
848
|
if (!file.endsWith(".json"))
|
|
956
849
|
continue;
|
|
957
850
|
try {
|
|
958
|
-
const content = readFileSync(
|
|
851
|
+
const content = readFileSync(join2(messageDir, file), "utf-8");
|
|
959
852
|
messages.push(JSON.parse(content));
|
|
960
853
|
} catch {
|
|
961
854
|
continue;
|
|
@@ -963,8 +856,8 @@ function readMessagesFromStorage(sessionID) {
|
|
|
963
856
|
}
|
|
964
857
|
return messages.sort((a, b) => a.id.localeCompare(b.id));
|
|
965
858
|
}
|
|
966
|
-
function
|
|
967
|
-
const partDir =
|
|
859
|
+
function readParts(messageID) {
|
|
860
|
+
const partDir = join2(PART_STORAGE, messageID);
|
|
968
861
|
if (!existsSync(partDir))
|
|
969
862
|
return [];
|
|
970
863
|
const parts = [];
|
|
@@ -972,7 +865,7 @@ function readPartsFromStorage(messageID) {
|
|
|
972
865
|
if (!file.endsWith(".json"))
|
|
973
866
|
continue;
|
|
974
867
|
try {
|
|
975
|
-
const content = readFileSync(
|
|
868
|
+
const content = readFileSync(join2(partDir, file), "utf-8");
|
|
976
869
|
parts.push(JSON.parse(content));
|
|
977
870
|
} catch {
|
|
978
871
|
continue;
|
|
@@ -980,8 +873,29 @@ function readPartsFromStorage(messageID) {
|
|
|
980
873
|
}
|
|
981
874
|
return parts;
|
|
982
875
|
}
|
|
983
|
-
function
|
|
984
|
-
|
|
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);
|
|
985
899
|
if (!existsSync(partDir)) {
|
|
986
900
|
mkdirSync(partDir, { recursive: true });
|
|
987
901
|
}
|
|
@@ -991,17 +905,19 @@ function injectTextPartToStorage(sessionID, messageID, text) {
|
|
|
991
905
|
sessionID,
|
|
992
906
|
messageID,
|
|
993
907
|
type: "text",
|
|
994
|
-
text
|
|
908
|
+
text,
|
|
909
|
+
synthetic: true
|
|
995
910
|
};
|
|
996
911
|
try {
|
|
997
|
-
writeFileSync(
|
|
912
|
+
writeFileSync(join2(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
|
|
998
913
|
return true;
|
|
999
914
|
} catch {
|
|
1000
915
|
return false;
|
|
1001
916
|
}
|
|
1002
917
|
}
|
|
1003
|
-
function
|
|
1004
|
-
const messages =
|
|
918
|
+
function findEmptyMessages(sessionID) {
|
|
919
|
+
const messages = readMessages(sessionID);
|
|
920
|
+
const emptyIds = [];
|
|
1005
921
|
for (let i = 0;i < messages.length; i++) {
|
|
1006
922
|
const msg = messages[i];
|
|
1007
923
|
if (msg.role !== "assistant")
|
|
@@ -1009,72 +925,187 @@ function findEmptyContentMessageFromStorage(sessionID) {
|
|
|
1009
925
|
const isLastMessage = i === messages.length - 1;
|
|
1010
926
|
if (isLastMessage)
|
|
1011
927
|
continue;
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
if (THINKING_TYPES.has(p.type))
|
|
1015
|
-
return false;
|
|
1016
|
-
if (META_TYPES.has(p.type))
|
|
1017
|
-
return false;
|
|
1018
|
-
if (p.type === "text" && p.text?.trim())
|
|
1019
|
-
return true;
|
|
1020
|
-
if (p.type === "tool_use")
|
|
1021
|
-
return true;
|
|
1022
|
-
if (p.type === "tool_result")
|
|
1023
|
-
return true;
|
|
1024
|
-
return false;
|
|
1025
|
-
});
|
|
1026
|
-
if (!hasContent && parts.length > 0) {
|
|
1027
|
-
return msg.id;
|
|
928
|
+
if (!messageHasContent(msg.id)) {
|
|
929
|
+
emptyIds.push(msg.id);
|
|
1028
930
|
}
|
|
1029
931
|
}
|
|
1030
|
-
return
|
|
932
|
+
return emptyIds;
|
|
1031
933
|
}
|
|
1032
|
-
|
|
1033
|
-
const
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
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;
|
|
1037
951
|
}
|
|
1038
|
-
|
|
1039
|
-
const
|
|
1040
|
-
const
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
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
|
+
}
|
|
1047
972
|
}
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
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 });
|
|
1051
979
|
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
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;
|
|
1057
1011
|
}
|
|
1012
|
+
} catch {
|
|
1013
|
+
continue;
|
|
1058
1014
|
}
|
|
1059
1015
|
}
|
|
1060
|
-
|
|
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) {
|
|
1061
1051
|
return false;
|
|
1062
1052
|
}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
});
|
|
1068
|
-
|
|
1069
|
-
|
|
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 {
|
|
1070
1065
|
return false;
|
|
1071
1066
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1067
|
+
}
|
|
1068
|
+
async function recoverThinkingBlockOrder(_client, sessionID, _failedAssistantMsg, _directory) {
|
|
1069
|
+
const orphanMessages = findMessagesWithOrphanThinking(sessionID);
|
|
1070
|
+
if (orphanMessages.length === 0) {
|
|
1071
|
+
return false;
|
|
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;
|
|
1078
1109
|
}
|
|
1079
1110
|
function createSessionRecoveryHook(ctx) {
|
|
1080
1111
|
const processingErrors = new Set;
|
|
@@ -1122,14 +1153,12 @@ function createSessionRecoveryHook(ctx) {
|
|
|
1122
1153
|
tool_result_missing: "Injecting cancelled tool results...",
|
|
1123
1154
|
thinking_block_order: "Fixing message structure...",
|
|
1124
1155
|
thinking_disabled_violation: "Stripping thinking blocks...",
|
|
1125
|
-
empty_content_message: "
|
|
1156
|
+
empty_content_message: "Fixing empty message..."
|
|
1126
1157
|
};
|
|
1127
|
-
const toastTitle = toastTitles[errorType];
|
|
1128
|
-
const toastMessage = toastMessages[errorType];
|
|
1129
1158
|
await ctx.client.tui.showToast({
|
|
1130
1159
|
body: {
|
|
1131
|
-
title:
|
|
1132
|
-
message:
|
|
1160
|
+
title: toastTitles[errorType],
|
|
1161
|
+
message: toastMessages[errorType],
|
|
1133
1162
|
variant: "warning",
|
|
1134
1163
|
duration: 3000
|
|
1135
1164
|
}
|
|
@@ -1160,14 +1189,14 @@ function createSessionRecoveryHook(ctx) {
|
|
|
1160
1189
|
// src/hooks/comment-checker/cli.ts
|
|
1161
1190
|
var {spawn: spawn2 } = globalThis.Bun;
|
|
1162
1191
|
import { createRequire as createRequire2 } from "module";
|
|
1163
|
-
import { dirname, join as
|
|
1192
|
+
import { dirname, join as join4 } from "path";
|
|
1164
1193
|
import { existsSync as existsSync3 } from "fs";
|
|
1165
1194
|
import * as fs from "fs";
|
|
1166
1195
|
|
|
1167
1196
|
// src/hooks/comment-checker/downloader.ts
|
|
1168
1197
|
var {spawn } = globalThis.Bun;
|
|
1169
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, chmodSync, unlinkSync, appendFileSync } from "fs";
|
|
1170
|
-
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";
|
|
1171
1200
|
import { homedir } from "os";
|
|
1172
1201
|
import { createRequire } from "module";
|
|
1173
1202
|
var DEBUG = process.env.COMMENT_CHECKER_DEBUG === "1";
|
|
@@ -1189,14 +1218,14 @@ var PLATFORM_MAP = {
|
|
|
1189
1218
|
};
|
|
1190
1219
|
function getCacheDir() {
|
|
1191
1220
|
const xdgCache2 = process.env.XDG_CACHE_HOME;
|
|
1192
|
-
const base = xdgCache2 ||
|
|
1193
|
-
return
|
|
1221
|
+
const base = xdgCache2 || join3(homedir(), ".cache");
|
|
1222
|
+
return join3(base, "oh-my-opencode", "bin");
|
|
1194
1223
|
}
|
|
1195
1224
|
function getBinaryName() {
|
|
1196
1225
|
return process.platform === "win32" ? "comment-checker.exe" : "comment-checker";
|
|
1197
1226
|
}
|
|
1198
1227
|
function getCachedBinaryPath() {
|
|
1199
|
-
const binaryPath =
|
|
1228
|
+
const binaryPath = join3(getCacheDir(), getBinaryName());
|
|
1200
1229
|
return existsSync2(binaryPath) ? binaryPath : null;
|
|
1201
1230
|
}
|
|
1202
1231
|
function getPackageVersion() {
|
|
@@ -1244,7 +1273,7 @@ async function downloadCommentChecker() {
|
|
|
1244
1273
|
}
|
|
1245
1274
|
const cacheDir = getCacheDir();
|
|
1246
1275
|
const binaryName = getBinaryName();
|
|
1247
|
-
const binaryPath =
|
|
1276
|
+
const binaryPath = join3(cacheDir, binaryName);
|
|
1248
1277
|
if (existsSync2(binaryPath)) {
|
|
1249
1278
|
debugLog("Binary already cached at:", binaryPath);
|
|
1250
1279
|
return binaryPath;
|
|
@@ -1263,7 +1292,7 @@ async function downloadCommentChecker() {
|
|
|
1263
1292
|
if (!response.ok) {
|
|
1264
1293
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
1265
1294
|
}
|
|
1266
|
-
const archivePath =
|
|
1295
|
+
const archivePath = join3(cacheDir, assetName);
|
|
1267
1296
|
const arrayBuffer = await response.arrayBuffer();
|
|
1268
1297
|
await Bun.write(archivePath, arrayBuffer);
|
|
1269
1298
|
debugLog(`Downloaded archive to: ${archivePath}`);
|
|
@@ -1273,7 +1302,7 @@ async function downloadCommentChecker() {
|
|
|
1273
1302
|
await extractZip(archivePath, cacheDir);
|
|
1274
1303
|
}
|
|
1275
1304
|
if (existsSync2(archivePath)) {
|
|
1276
|
-
|
|
1305
|
+
unlinkSync2(archivePath);
|
|
1277
1306
|
}
|
|
1278
1307
|
if (process.platform !== "win32" && existsSync2(binaryPath)) {
|
|
1279
1308
|
chmodSync(binaryPath, 493);
|
|
@@ -1328,7 +1357,7 @@ function findCommentCheckerPathSync() {
|
|
|
1328
1357
|
const require2 = createRequire2(import.meta.url);
|
|
1329
1358
|
const cliPkgPath = require2.resolve("@code-yeongyu/comment-checker/package.json");
|
|
1330
1359
|
const cliDir = dirname(cliPkgPath);
|
|
1331
|
-
const binaryPath =
|
|
1360
|
+
const binaryPath = join4(cliDir, "bin", binaryName);
|
|
1332
1361
|
if (existsSync3(binaryPath)) {
|
|
1333
1362
|
debugLog2("found binary in main package:", binaryPath);
|
|
1334
1363
|
return binaryPath;
|
|
@@ -1342,7 +1371,7 @@ function findCommentCheckerPathSync() {
|
|
|
1342
1371
|
const require2 = createRequire2(import.meta.url);
|
|
1343
1372
|
const pkgPath = require2.resolve(`${platformPkg}/package.json`);
|
|
1344
1373
|
const pkgDir = dirname(pkgPath);
|
|
1345
|
-
const binaryPath =
|
|
1374
|
+
const binaryPath = join4(pkgDir, "bin", binaryName);
|
|
1346
1375
|
if (existsSync3(binaryPath)) {
|
|
1347
1376
|
debugLog2("found binary in platform package:", binaryPath);
|
|
1348
1377
|
return binaryPath;
|
|
@@ -1566,6 +1595,370 @@ ${result.message}`;
|
|
|
1566
1595
|
debugLog3("CLI: no comments detected");
|
|
1567
1596
|
}
|
|
1568
1597
|
}
|
|
1598
|
+
// src/hooks/grep-output-truncator.ts
|
|
1599
|
+
var ANTHROPIC_ACTUAL_LIMIT2 = 200000;
|
|
1600
|
+
var CHARS_PER_TOKEN_ESTIMATE = 4;
|
|
1601
|
+
var TARGET_MAX_TOKENS = 50000;
|
|
1602
|
+
function estimateTokens(text) {
|
|
1603
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN_ESTIMATE);
|
|
1604
|
+
}
|
|
1605
|
+
function truncateToTokenLimit(output, maxTokens) {
|
|
1606
|
+
const currentTokens = estimateTokens(output);
|
|
1607
|
+
if (currentTokens <= maxTokens) {
|
|
1608
|
+
return { result: output, truncated: false };
|
|
1609
|
+
}
|
|
1610
|
+
const lines = output.split(`
|
|
1611
|
+
`);
|
|
1612
|
+
if (lines.length <= 3) {
|
|
1613
|
+
const maxChars = maxTokens * CHARS_PER_TOKEN_ESTIMATE;
|
|
1614
|
+
return {
|
|
1615
|
+
result: output.slice(0, maxChars) + `
|
|
1616
|
+
|
|
1617
|
+
[Output truncated due to context window limit]`,
|
|
1618
|
+
truncated: true
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
const headerLines = lines.slice(0, 3);
|
|
1622
|
+
const contentLines = lines.slice(3);
|
|
1623
|
+
const headerText = headerLines.join(`
|
|
1624
|
+
`);
|
|
1625
|
+
const headerTokens = estimateTokens(headerText);
|
|
1626
|
+
const availableTokens = maxTokens - headerTokens - 50;
|
|
1627
|
+
if (availableTokens <= 0) {
|
|
1628
|
+
return {
|
|
1629
|
+
result: headerText + `
|
|
1630
|
+
|
|
1631
|
+
[Content truncated due to context window limit]`,
|
|
1632
|
+
truncated: true
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
let resultLines = [];
|
|
1636
|
+
let currentTokenCount = 0;
|
|
1637
|
+
for (const line of contentLines) {
|
|
1638
|
+
const lineTokens = estimateTokens(line + `
|
|
1639
|
+
`);
|
|
1640
|
+
if (currentTokenCount + lineTokens > availableTokens) {
|
|
1641
|
+
break;
|
|
1642
|
+
}
|
|
1643
|
+
resultLines.push(line);
|
|
1644
|
+
currentTokenCount += lineTokens;
|
|
1645
|
+
}
|
|
1646
|
+
const truncatedContent = [...headerLines, ...resultLines].join(`
|
|
1647
|
+
`);
|
|
1648
|
+
const removedCount = contentLines.length - resultLines.length;
|
|
1649
|
+
return {
|
|
1650
|
+
result: truncatedContent + `
|
|
1651
|
+
|
|
1652
|
+
[${removedCount} more lines truncated due to context window limit]`,
|
|
1653
|
+
truncated: true
|
|
1654
|
+
};
|
|
1655
|
+
}
|
|
1656
|
+
function createGrepOutputTruncatorHook(ctx) {
|
|
1657
|
+
const GREP_TOOLS = ["safe_grep", "Grep"];
|
|
1658
|
+
const toolExecuteAfter = async (input, output) => {
|
|
1659
|
+
if (!GREP_TOOLS.includes(input.tool))
|
|
1660
|
+
return;
|
|
1661
|
+
const { sessionID } = input;
|
|
1662
|
+
try {
|
|
1663
|
+
const response = await ctx.client.session.messages({
|
|
1664
|
+
path: { id: sessionID }
|
|
1665
|
+
});
|
|
1666
|
+
const messages = response.data ?? response;
|
|
1667
|
+
const assistantMessages = messages.filter((m) => m.info.role === "assistant").map((m) => m.info);
|
|
1668
|
+
if (assistantMessages.length === 0)
|
|
1669
|
+
return;
|
|
1670
|
+
const totalInputTokens = assistantMessages.reduce((sum, m) => {
|
|
1671
|
+
const inputTokens = m.tokens?.input ?? 0;
|
|
1672
|
+
const cacheReadTokens = m.tokens?.cache?.read ?? 0;
|
|
1673
|
+
return sum + inputTokens + cacheReadTokens;
|
|
1674
|
+
}, 0);
|
|
1675
|
+
const remainingTokens = ANTHROPIC_ACTUAL_LIMIT2 - totalInputTokens;
|
|
1676
|
+
const maxOutputTokens = Math.min(remainingTokens * 0.5, TARGET_MAX_TOKENS);
|
|
1677
|
+
if (maxOutputTokens <= 0) {
|
|
1678
|
+
output.output = "[Output suppressed - context window exhausted]";
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
const { result, truncated } = truncateToTokenLimit(output.output, maxOutputTokens);
|
|
1682
|
+
if (truncated) {
|
|
1683
|
+
output.output = result;
|
|
1684
|
+
}
|
|
1685
|
+
} catch {}
|
|
1686
|
+
};
|
|
1687
|
+
return {
|
|
1688
|
+
"tool.execute.after": toolExecuteAfter
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
// src/hooks/pulse-monitor.ts
|
|
1692
|
+
function createPulseMonitorHook(ctx) {
|
|
1693
|
+
const STANDARD_TIMEOUT = 5 * 60 * 1000;
|
|
1694
|
+
const THINKING_TIMEOUT = 5 * 60 * 1000;
|
|
1695
|
+
const CHECK_INTERVAL = 5 * 1000;
|
|
1696
|
+
let lastHeartbeat = Date.now();
|
|
1697
|
+
let isMonitoring = false;
|
|
1698
|
+
let currentSessionID = null;
|
|
1699
|
+
let monitorTimer = null;
|
|
1700
|
+
let isThinking = false;
|
|
1701
|
+
const startMonitoring = (sessionID) => {
|
|
1702
|
+
if (currentSessionID !== sessionID) {
|
|
1703
|
+
currentSessionID = sessionID;
|
|
1704
|
+
isThinking = false;
|
|
1705
|
+
}
|
|
1706
|
+
lastHeartbeat = Date.now();
|
|
1707
|
+
if (!isMonitoring) {
|
|
1708
|
+
isMonitoring = true;
|
|
1709
|
+
if (monitorTimer)
|
|
1710
|
+
clearInterval(monitorTimer);
|
|
1711
|
+
monitorTimer = setInterval(async () => {
|
|
1712
|
+
if (!isMonitoring || !currentSessionID)
|
|
1713
|
+
return;
|
|
1714
|
+
const timeSinceLastHeartbeat = Date.now() - lastHeartbeat;
|
|
1715
|
+
const currentTimeout = isThinking ? THINKING_TIMEOUT : STANDARD_TIMEOUT;
|
|
1716
|
+
if (timeSinceLastHeartbeat > currentTimeout) {
|
|
1717
|
+
await recoverStalledSession(currentSessionID, timeSinceLastHeartbeat, isThinking);
|
|
1718
|
+
}
|
|
1719
|
+
}, CHECK_INTERVAL);
|
|
1720
|
+
}
|
|
1721
|
+
};
|
|
1722
|
+
const stopMonitoring = () => {
|
|
1723
|
+
isMonitoring = false;
|
|
1724
|
+
if (monitorTimer) {
|
|
1725
|
+
clearInterval(monitorTimer);
|
|
1726
|
+
monitorTimer = null;
|
|
1727
|
+
}
|
|
1728
|
+
};
|
|
1729
|
+
const updateHeartbeat = (isThinkingUpdate) => {
|
|
1730
|
+
if (isMonitoring) {
|
|
1731
|
+
lastHeartbeat = Date.now();
|
|
1732
|
+
if (isThinkingUpdate !== undefined) {
|
|
1733
|
+
isThinking = isThinkingUpdate;
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
};
|
|
1737
|
+
const recoverStalledSession = async (sessionID, stalledDuration, wasThinking) => {
|
|
1738
|
+
stopMonitoring();
|
|
1739
|
+
try {
|
|
1740
|
+
const durationSec = Math.round(stalledDuration / 1000);
|
|
1741
|
+
const typeStr = wasThinking ? "Thinking" : "Standard";
|
|
1742
|
+
await ctx.client.tui.showToast({
|
|
1743
|
+
body: {
|
|
1744
|
+
title: "Pulse Monitor: Cardiac Arrest",
|
|
1745
|
+
message: `Session stalled (${typeStr}) for ${durationSec}s. Defibrillating...`,
|
|
1746
|
+
variant: "error",
|
|
1747
|
+
duration: 5000
|
|
1748
|
+
}
|
|
1749
|
+
}).catch(() => {});
|
|
1750
|
+
await ctx.client.session.abort({ path: { id: sessionID } }).catch(() => {});
|
|
1751
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
1752
|
+
await ctx.client.session.prompt({
|
|
1753
|
+
path: { id: sessionID },
|
|
1754
|
+
body: { parts: [{ type: "text", text: "The connection was unstable and stalled. Please continue from where you left off." }] },
|
|
1755
|
+
query: { directory: ctx.directory }
|
|
1756
|
+
});
|
|
1757
|
+
startMonitoring(sessionID);
|
|
1758
|
+
} catch (err) {
|
|
1759
|
+
console.error("[PulseMonitor] Recovery failed:", err);
|
|
1760
|
+
stopMonitoring();
|
|
1761
|
+
}
|
|
1762
|
+
};
|
|
1763
|
+
return {
|
|
1764
|
+
event: async (input) => {
|
|
1765
|
+
const { event } = input;
|
|
1766
|
+
const props = event.properties;
|
|
1767
|
+
if (event.type === "session.updated" || event.type === "message.part.updated") {
|
|
1768
|
+
const sessionID = props?.info?.id || props?.sessionID;
|
|
1769
|
+
if (sessionID) {
|
|
1770
|
+
if (!isMonitoring)
|
|
1771
|
+
startMonitoring(sessionID);
|
|
1772
|
+
let thinkingUpdate = undefined;
|
|
1773
|
+
if (event.type === "message.part.updated") {
|
|
1774
|
+
const part = props?.part;
|
|
1775
|
+
if (part) {
|
|
1776
|
+
const THINKING_TYPES2 = ["thinking", "redacted_thinking", "reasoning"];
|
|
1777
|
+
if (THINKING_TYPES2.includes(part.type)) {
|
|
1778
|
+
thinkingUpdate = true;
|
|
1779
|
+
} else if (part.type === "text" || part.type === "tool_use") {
|
|
1780
|
+
thinkingUpdate = false;
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
updateHeartbeat(thinkingUpdate);
|
|
1785
|
+
}
|
|
1786
|
+
} else if (event.type === "session.idle" || event.type === "session.error" || event.type === "session.stopped") {
|
|
1787
|
+
stopMonitoring();
|
|
1788
|
+
}
|
|
1789
|
+
},
|
|
1790
|
+
"tool.execute.before": async () => {
|
|
1791
|
+
stopMonitoring();
|
|
1792
|
+
},
|
|
1793
|
+
"tool.execute.after": async (input) => {
|
|
1794
|
+
if (input.sessionID) {
|
|
1795
|
+
startMonitoring(input.sessionID);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
};
|
|
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
|
+
}
|
|
1569
1962
|
// src/features/terminal/title.ts
|
|
1570
1963
|
var STATUS_ICONS = {
|
|
1571
1964
|
ready: "",
|
|
@@ -1774,17 +2167,31 @@ var EXT_TO_LANG = {
|
|
|
1774
2167
|
".svelte": "svelte",
|
|
1775
2168
|
".astro": "astro",
|
|
1776
2169
|
".yaml": "yaml",
|
|
1777
|
-
".yml": "yaml"
|
|
2170
|
+
".yml": "yaml",
|
|
2171
|
+
".json": "json",
|
|
2172
|
+
".jsonc": "jsonc",
|
|
2173
|
+
".html": "html",
|
|
2174
|
+
".htm": "html",
|
|
2175
|
+
".css": "css",
|
|
2176
|
+
".scss": "scss",
|
|
2177
|
+
".less": "less",
|
|
2178
|
+
".sh": "shellscript",
|
|
2179
|
+
".bash": "shellscript",
|
|
2180
|
+
".zsh": "shellscript",
|
|
2181
|
+
".fish": "fish",
|
|
2182
|
+
".md": "markdown",
|
|
2183
|
+
".tf": "terraform",
|
|
2184
|
+
".tfvars": "terraform"
|
|
1778
2185
|
};
|
|
1779
2186
|
// src/tools/lsp/config.ts
|
|
1780
|
-
import { existsSync as
|
|
1781
|
-
import { join as
|
|
2187
|
+
import { existsSync as existsSync7, readFileSync as readFileSync4 } from "fs";
|
|
2188
|
+
import { join as join8 } from "path";
|
|
1782
2189
|
import { homedir as homedir2 } from "os";
|
|
1783
2190
|
function loadJsonFile(path2) {
|
|
1784
|
-
if (!
|
|
2191
|
+
if (!existsSync7(path2))
|
|
1785
2192
|
return null;
|
|
1786
2193
|
try {
|
|
1787
|
-
return JSON.parse(
|
|
2194
|
+
return JSON.parse(readFileSync4(path2, "utf-8"));
|
|
1788
2195
|
} catch {
|
|
1789
2196
|
return null;
|
|
1790
2197
|
}
|
|
@@ -1792,9 +2199,9 @@ function loadJsonFile(path2) {
|
|
|
1792
2199
|
function getConfigPaths() {
|
|
1793
2200
|
const cwd = process.cwd();
|
|
1794
2201
|
return {
|
|
1795
|
-
project:
|
|
1796
|
-
user:
|
|
1797
|
-
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")
|
|
1798
2205
|
};
|
|
1799
2206
|
}
|
|
1800
2207
|
function loadAllConfigs() {
|
|
@@ -1887,7 +2294,7 @@ function isServerInstalled(command) {
|
|
|
1887
2294
|
const pathEnv = process.env.PATH || "";
|
|
1888
2295
|
const paths = pathEnv.split(":");
|
|
1889
2296
|
for (const p of paths) {
|
|
1890
|
-
if (
|
|
2297
|
+
if (existsSync7(join8(p, cmd))) {
|
|
1891
2298
|
return true;
|
|
1892
2299
|
}
|
|
1893
2300
|
}
|
|
@@ -1937,8 +2344,8 @@ function getAllServers() {
|
|
|
1937
2344
|
}
|
|
1938
2345
|
// src/tools/lsp/client.ts
|
|
1939
2346
|
var {spawn: spawn3 } = globalThis.Bun;
|
|
1940
|
-
import { readFileSync as
|
|
1941
|
-
import { extname, resolve } from "path";
|
|
2347
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
2348
|
+
import { extname, resolve as resolve2 } from "path";
|
|
1942
2349
|
class LSPServerManager {
|
|
1943
2350
|
static instance;
|
|
1944
2351
|
clients = new Map;
|
|
@@ -2067,6 +2474,7 @@ class LSPClient {
|
|
|
2067
2474
|
openedFiles = new Set;
|
|
2068
2475
|
stderrBuffer = [];
|
|
2069
2476
|
processExited = false;
|
|
2477
|
+
diagnosticsStore = new Map;
|
|
2070
2478
|
constructor(root, server) {
|
|
2071
2479
|
this.root = root;
|
|
2072
2480
|
this.server = server;
|
|
@@ -2087,7 +2495,7 @@ class LSPClient {
|
|
|
2087
2495
|
}
|
|
2088
2496
|
this.startReading();
|
|
2089
2497
|
this.startStderrReading();
|
|
2090
|
-
await new Promise((
|
|
2498
|
+
await new Promise((resolve3) => setTimeout(resolve3, 100));
|
|
2091
2499
|
if (this.proc.exitCode !== null) {
|
|
2092
2500
|
const stderr = this.stderrBuffer.join(`
|
|
2093
2501
|
`);
|
|
@@ -2191,7 +2599,11 @@ stderr: ${stderr}` : ""));
|
|
|
2191
2599
|
this.buffer = this.buffer.slice(end);
|
|
2192
2600
|
try {
|
|
2193
2601
|
const msg = JSON.parse(content);
|
|
2194
|
-
if ("
|
|
2602
|
+
if ("method" in msg && !("id" in msg)) {
|
|
2603
|
+
if (msg.method === "textDocument/publishDiagnostics" && msg.params?.uri) {
|
|
2604
|
+
this.diagnosticsStore.set(msg.params.uri, msg.params.diagnostics ?? []);
|
|
2605
|
+
}
|
|
2606
|
+
} else if ("id" in msg && "method" in msg) {
|
|
2195
2607
|
this.handleServerRequest(msg.id, msg.method, msg.params);
|
|
2196
2608
|
} else if ("id" in msg && this.pending.has(msg.id)) {
|
|
2197
2609
|
const handler = this.pending.get(msg.id);
|
|
@@ -2220,8 +2632,8 @@ stderr: ${stderr}` : ""));
|
|
|
2220
2632
|
\r
|
|
2221
2633
|
`;
|
|
2222
2634
|
this.proc.stdin.write(header + msg);
|
|
2223
|
-
return new Promise((
|
|
2224
|
-
this.pending.set(id, { resolve:
|
|
2635
|
+
return new Promise((resolve3, reject) => {
|
|
2636
|
+
this.pending.set(id, { resolve: resolve3, reject });
|
|
2225
2637
|
setTimeout(() => {
|
|
2226
2638
|
if (this.pending.has(id)) {
|
|
2227
2639
|
this.pending.delete(id);
|
|
@@ -2253,9 +2665,15 @@ ${msg}`);
|
|
|
2253
2665
|
\r
|
|
2254
2666
|
${msg}`);
|
|
2255
2667
|
}
|
|
2256
|
-
handleServerRequest(id, method,
|
|
2668
|
+
handleServerRequest(id, method, params) {
|
|
2257
2669
|
if (method === "workspace/configuration") {
|
|
2258
|
-
|
|
2670
|
+
const items = params?.items ?? [];
|
|
2671
|
+
const result = items.map((item) => {
|
|
2672
|
+
if (item.section === "json")
|
|
2673
|
+
return { validate: { enable: true } };
|
|
2674
|
+
return {};
|
|
2675
|
+
});
|
|
2676
|
+
this.respond(id, result);
|
|
2259
2677
|
} else if (method === "client/registerCapability") {
|
|
2260
2678
|
this.respond(id, null);
|
|
2261
2679
|
} else if (method === "window/workDoneProgress/create") {
|
|
@@ -2317,14 +2735,16 @@ ${msg}`);
|
|
|
2317
2735
|
...this.server.initialization
|
|
2318
2736
|
});
|
|
2319
2737
|
this.notify("initialized");
|
|
2320
|
-
this.notify("workspace/didChangeConfiguration", {
|
|
2738
|
+
this.notify("workspace/didChangeConfiguration", {
|
|
2739
|
+
settings: { json: { validate: { enable: true } } }
|
|
2740
|
+
});
|
|
2321
2741
|
await new Promise((r) => setTimeout(r, 300));
|
|
2322
2742
|
}
|
|
2323
2743
|
async openFile(filePath) {
|
|
2324
|
-
const absPath =
|
|
2744
|
+
const absPath = resolve2(filePath);
|
|
2325
2745
|
if (this.openedFiles.has(absPath))
|
|
2326
2746
|
return;
|
|
2327
|
-
const text =
|
|
2747
|
+
const text = readFileSync5(absPath, "utf-8");
|
|
2328
2748
|
const ext = extname(absPath);
|
|
2329
2749
|
const languageId = getLanguageId(ext);
|
|
2330
2750
|
this.notify("textDocument/didOpen", {
|
|
@@ -2339,7 +2759,7 @@ ${msg}`);
|
|
|
2339
2759
|
await new Promise((r) => setTimeout(r, 1000));
|
|
2340
2760
|
}
|
|
2341
2761
|
async hover(filePath, line, character) {
|
|
2342
|
-
const absPath =
|
|
2762
|
+
const absPath = resolve2(filePath);
|
|
2343
2763
|
await this.openFile(absPath);
|
|
2344
2764
|
return this.send("textDocument/hover", {
|
|
2345
2765
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -2347,7 +2767,7 @@ ${msg}`);
|
|
|
2347
2767
|
});
|
|
2348
2768
|
}
|
|
2349
2769
|
async definition(filePath, line, character) {
|
|
2350
|
-
const absPath =
|
|
2770
|
+
const absPath = resolve2(filePath);
|
|
2351
2771
|
await this.openFile(absPath);
|
|
2352
2772
|
return this.send("textDocument/definition", {
|
|
2353
2773
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -2355,7 +2775,7 @@ ${msg}`);
|
|
|
2355
2775
|
});
|
|
2356
2776
|
}
|
|
2357
2777
|
async references(filePath, line, character, includeDeclaration = true) {
|
|
2358
|
-
const absPath =
|
|
2778
|
+
const absPath = resolve2(filePath);
|
|
2359
2779
|
await this.openFile(absPath);
|
|
2360
2780
|
return this.send("textDocument/references", {
|
|
2361
2781
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -2364,7 +2784,7 @@ ${msg}`);
|
|
|
2364
2784
|
});
|
|
2365
2785
|
}
|
|
2366
2786
|
async documentSymbols(filePath) {
|
|
2367
|
-
const absPath =
|
|
2787
|
+
const absPath = resolve2(filePath);
|
|
2368
2788
|
await this.openFile(absPath);
|
|
2369
2789
|
return this.send("textDocument/documentSymbol", {
|
|
2370
2790
|
textDocument: { uri: `file://${absPath}` }
|
|
@@ -2374,15 +2794,22 @@ ${msg}`);
|
|
|
2374
2794
|
return this.send("workspace/symbol", { query });
|
|
2375
2795
|
}
|
|
2376
2796
|
async diagnostics(filePath) {
|
|
2377
|
-
const absPath =
|
|
2797
|
+
const absPath = resolve2(filePath);
|
|
2798
|
+
const uri = `file://${absPath}`;
|
|
2378
2799
|
await this.openFile(absPath);
|
|
2379
2800
|
await new Promise((r) => setTimeout(r, 500));
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2801
|
+
try {
|
|
2802
|
+
const result = await this.send("textDocument/diagnostic", {
|
|
2803
|
+
textDocument: { uri }
|
|
2804
|
+
});
|
|
2805
|
+
if (result && typeof result === "object" && "items" in result) {
|
|
2806
|
+
return result;
|
|
2807
|
+
}
|
|
2808
|
+
} catch {}
|
|
2809
|
+
return { items: this.diagnosticsStore.get(uri) ?? [] };
|
|
2383
2810
|
}
|
|
2384
2811
|
async prepareRename(filePath, line, character) {
|
|
2385
|
-
const absPath =
|
|
2812
|
+
const absPath = resolve2(filePath);
|
|
2386
2813
|
await this.openFile(absPath);
|
|
2387
2814
|
return this.send("textDocument/prepareRename", {
|
|
2388
2815
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -2390,7 +2817,7 @@ ${msg}`);
|
|
|
2390
2817
|
});
|
|
2391
2818
|
}
|
|
2392
2819
|
async rename(filePath, line, character, newName) {
|
|
2393
|
-
const absPath =
|
|
2820
|
+
const absPath = resolve2(filePath);
|
|
2394
2821
|
await this.openFile(absPath);
|
|
2395
2822
|
return this.send("textDocument/rename", {
|
|
2396
2823
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -2399,7 +2826,7 @@ ${msg}`);
|
|
|
2399
2826
|
});
|
|
2400
2827
|
}
|
|
2401
2828
|
async codeAction(filePath, startLine, startChar, endLine, endChar, only) {
|
|
2402
|
-
const absPath =
|
|
2829
|
+
const absPath = resolve2(filePath);
|
|
2403
2830
|
await this.openFile(absPath);
|
|
2404
2831
|
return this.send("textDocument/codeAction", {
|
|
2405
2832
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -2427,29 +2854,30 @@ ${msg}`);
|
|
|
2427
2854
|
this.proc?.kill();
|
|
2428
2855
|
this.proc = null;
|
|
2429
2856
|
this.processExited = true;
|
|
2857
|
+
this.diagnosticsStore.clear();
|
|
2430
2858
|
}
|
|
2431
2859
|
}
|
|
2432
2860
|
// src/tools/lsp/utils.ts
|
|
2433
|
-
import { extname as extname2, resolve as
|
|
2434
|
-
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";
|
|
2435
2863
|
function findWorkspaceRoot(filePath) {
|
|
2436
|
-
let dir =
|
|
2437
|
-
if (!
|
|
2864
|
+
let dir = resolve3(filePath);
|
|
2865
|
+
if (!existsSync8(dir) || !__require("fs").statSync(dir).isDirectory()) {
|
|
2438
2866
|
dir = __require("path").dirname(dir);
|
|
2439
2867
|
}
|
|
2440
2868
|
const markers = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
|
|
2441
2869
|
while (dir !== "/") {
|
|
2442
2870
|
for (const marker of markers) {
|
|
2443
|
-
if (
|
|
2871
|
+
if (existsSync8(__require("path").join(dir, marker))) {
|
|
2444
2872
|
return dir;
|
|
2445
2873
|
}
|
|
2446
2874
|
}
|
|
2447
2875
|
dir = __require("path").dirname(dir);
|
|
2448
2876
|
}
|
|
2449
|
-
return __require("path").dirname(
|
|
2877
|
+
return __require("path").dirname(resolve3(filePath));
|
|
2450
2878
|
}
|
|
2451
2879
|
async function withLspClient(filePath, fn) {
|
|
2452
|
-
const absPath =
|
|
2880
|
+
const absPath = resolve3(filePath);
|
|
2453
2881
|
const ext = extname2(absPath);
|
|
2454
2882
|
const server = findServerForExtension(ext);
|
|
2455
2883
|
if (!server) {
|
|
@@ -2598,7 +3026,7 @@ function formatCodeActions(actions) {
|
|
|
2598
3026
|
}
|
|
2599
3027
|
function applyTextEditsToFile(filePath, edits) {
|
|
2600
3028
|
try {
|
|
2601
|
-
let content =
|
|
3029
|
+
let content = readFileSync6(filePath, "utf-8");
|
|
2602
3030
|
const lines = content.split(`
|
|
2603
3031
|
`);
|
|
2604
3032
|
const sortedEdits = [...edits].sort((a, b) => {
|
|
@@ -2623,7 +3051,7 @@ function applyTextEditsToFile(filePath, edits) {
|
|
|
2623
3051
|
`));
|
|
2624
3052
|
}
|
|
2625
3053
|
}
|
|
2626
|
-
|
|
3054
|
+
writeFileSync3(filePath, lines.join(`
|
|
2627
3055
|
`), "utf-8");
|
|
2628
3056
|
return { success: true, editCount: edits.length };
|
|
2629
3057
|
} catch (err) {
|
|
@@ -2654,7 +3082,7 @@ function applyWorkspaceEdit(edit) {
|
|
|
2654
3082
|
if (change.kind === "create") {
|
|
2655
3083
|
try {
|
|
2656
3084
|
const filePath = change.uri.replace("file://", "");
|
|
2657
|
-
|
|
3085
|
+
writeFileSync3(filePath, "", "utf-8");
|
|
2658
3086
|
result.filesModified.push(filePath);
|
|
2659
3087
|
} catch (err) {
|
|
2660
3088
|
result.success = false;
|
|
@@ -2664,8 +3092,8 @@ function applyWorkspaceEdit(edit) {
|
|
|
2664
3092
|
try {
|
|
2665
3093
|
const oldPath = change.oldUri.replace("file://", "");
|
|
2666
3094
|
const newPath = change.newUri.replace("file://", "");
|
|
2667
|
-
const content =
|
|
2668
|
-
|
|
3095
|
+
const content = readFileSync6(oldPath, "utf-8");
|
|
3096
|
+
writeFileSync3(newPath, content, "utf-8");
|
|
2669
3097
|
__require("fs").unlinkSync(oldPath);
|
|
2670
3098
|
result.filesModified.push(newPath);
|
|
2671
3099
|
} catch (err) {
|
|
@@ -15365,13 +15793,13 @@ var lsp_code_action_resolve = tool({
|
|
|
15365
15793
|
});
|
|
15366
15794
|
// src/tools/ast-grep/constants.ts
|
|
15367
15795
|
import { createRequire as createRequire4 } from "module";
|
|
15368
|
-
import { dirname as
|
|
15369
|
-
import { existsSync as
|
|
15796
|
+
import { dirname as dirname3, join as join10 } from "path";
|
|
15797
|
+
import { existsSync as existsSync10, statSync } from "fs";
|
|
15370
15798
|
|
|
15371
15799
|
// src/tools/ast-grep/downloader.ts
|
|
15372
15800
|
var {spawn: spawn4 } = globalThis.Bun;
|
|
15373
|
-
import { existsSync as
|
|
15374
|
-
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";
|
|
15375
15803
|
import { homedir as homedir3 } from "os";
|
|
15376
15804
|
import { createRequire as createRequire3 } from "module";
|
|
15377
15805
|
var REPO2 = "ast-grep/ast-grep";
|
|
@@ -15397,19 +15825,19 @@ var PLATFORM_MAP2 = {
|
|
|
15397
15825
|
function getCacheDir2() {
|
|
15398
15826
|
if (process.platform === "win32") {
|
|
15399
15827
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
15400
|
-
const base2 = localAppData ||
|
|
15401
|
-
return
|
|
15828
|
+
const base2 = localAppData || join9(homedir3(), "AppData", "Local");
|
|
15829
|
+
return join9(base2, "oh-my-opencode", "bin");
|
|
15402
15830
|
}
|
|
15403
15831
|
const xdgCache2 = process.env.XDG_CACHE_HOME;
|
|
15404
|
-
const base = xdgCache2 ||
|
|
15405
|
-
return
|
|
15832
|
+
const base = xdgCache2 || join9(homedir3(), ".cache");
|
|
15833
|
+
return join9(base, "oh-my-opencode", "bin");
|
|
15406
15834
|
}
|
|
15407
15835
|
function getBinaryName3() {
|
|
15408
15836
|
return process.platform === "win32" ? "sg.exe" : "sg";
|
|
15409
15837
|
}
|
|
15410
15838
|
function getCachedBinaryPath2() {
|
|
15411
|
-
const binaryPath =
|
|
15412
|
-
return
|
|
15839
|
+
const binaryPath = join9(getCacheDir2(), getBinaryName3());
|
|
15840
|
+
return existsSync9(binaryPath) ? binaryPath : null;
|
|
15413
15841
|
}
|
|
15414
15842
|
async function extractZip2(archivePath, destDir) {
|
|
15415
15843
|
const proc = process.platform === "win32" ? spawn4([
|
|
@@ -15435,8 +15863,8 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
|
|
|
15435
15863
|
}
|
|
15436
15864
|
const cacheDir = getCacheDir2();
|
|
15437
15865
|
const binaryName = getBinaryName3();
|
|
15438
|
-
const binaryPath =
|
|
15439
|
-
if (
|
|
15866
|
+
const binaryPath = join9(cacheDir, binaryName);
|
|
15867
|
+
if (existsSync9(binaryPath)) {
|
|
15440
15868
|
return binaryPath;
|
|
15441
15869
|
}
|
|
15442
15870
|
const { arch, os: os2 } = platformInfo;
|
|
@@ -15444,21 +15872,21 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
|
|
|
15444
15872
|
const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
|
|
15445
15873
|
console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
|
|
15446
15874
|
try {
|
|
15447
|
-
if (!
|
|
15448
|
-
|
|
15875
|
+
if (!existsSync9(cacheDir)) {
|
|
15876
|
+
mkdirSync4(cacheDir, { recursive: true });
|
|
15449
15877
|
}
|
|
15450
15878
|
const response = await fetch(downloadUrl, { redirect: "follow" });
|
|
15451
15879
|
if (!response.ok) {
|
|
15452
15880
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
15453
15881
|
}
|
|
15454
|
-
const archivePath =
|
|
15882
|
+
const archivePath = join9(cacheDir, assetName);
|
|
15455
15883
|
const arrayBuffer = await response.arrayBuffer();
|
|
15456
15884
|
await Bun.write(archivePath, arrayBuffer);
|
|
15457
15885
|
await extractZip2(archivePath, cacheDir);
|
|
15458
|
-
if (
|
|
15459
|
-
|
|
15886
|
+
if (existsSync9(archivePath)) {
|
|
15887
|
+
unlinkSync4(archivePath);
|
|
15460
15888
|
}
|
|
15461
|
-
if (process.platform !== "win32" &&
|
|
15889
|
+
if (process.platform !== "win32" && existsSync9(binaryPath)) {
|
|
15462
15890
|
chmodSync2(binaryPath, 493);
|
|
15463
15891
|
}
|
|
15464
15892
|
console.log(`[oh-my-opencode] ast-grep binary ready.`);
|
|
@@ -15508,9 +15936,9 @@ function findSgCliPathSync() {
|
|
|
15508
15936
|
try {
|
|
15509
15937
|
const require2 = createRequire4(import.meta.url);
|
|
15510
15938
|
const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
|
|
15511
|
-
const cliDir =
|
|
15512
|
-
const sgPath =
|
|
15513
|
-
if (
|
|
15939
|
+
const cliDir = dirname3(cliPkgPath);
|
|
15940
|
+
const sgPath = join10(cliDir, binaryName);
|
|
15941
|
+
if (existsSync10(sgPath) && isValidBinary(sgPath)) {
|
|
15514
15942
|
return sgPath;
|
|
15515
15943
|
}
|
|
15516
15944
|
} catch {}
|
|
@@ -15519,10 +15947,10 @@ function findSgCliPathSync() {
|
|
|
15519
15947
|
try {
|
|
15520
15948
|
const require2 = createRequire4(import.meta.url);
|
|
15521
15949
|
const pkgPath = require2.resolve(`${platformPkg}/package.json`);
|
|
15522
|
-
const pkgDir =
|
|
15950
|
+
const pkgDir = dirname3(pkgPath);
|
|
15523
15951
|
const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
15524
|
-
const binaryPath =
|
|
15525
|
-
if (
|
|
15952
|
+
const binaryPath = join10(pkgDir, astGrepName);
|
|
15953
|
+
if (existsSync10(binaryPath) && isValidBinary(binaryPath)) {
|
|
15526
15954
|
return binaryPath;
|
|
15527
15955
|
}
|
|
15528
15956
|
} catch {}
|
|
@@ -15530,7 +15958,7 @@ function findSgCliPathSync() {
|
|
|
15530
15958
|
if (process.platform === "darwin") {
|
|
15531
15959
|
const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
|
|
15532
15960
|
for (const path2 of homebrewPaths) {
|
|
15533
|
-
if (
|
|
15961
|
+
if (existsSync10(path2) && isValidBinary(path2)) {
|
|
15534
15962
|
return path2;
|
|
15535
15963
|
}
|
|
15536
15964
|
}
|
|
@@ -15614,11 +16042,11 @@ var LANG_EXTENSIONS = {
|
|
|
15614
16042
|
|
|
15615
16043
|
// src/tools/ast-grep/cli.ts
|
|
15616
16044
|
var {spawn: spawn5 } = globalThis.Bun;
|
|
15617
|
-
import { existsSync as
|
|
16045
|
+
import { existsSync as existsSync11 } from "fs";
|
|
15618
16046
|
var resolvedCliPath3 = null;
|
|
15619
16047
|
var initPromise2 = null;
|
|
15620
16048
|
async function getAstGrepPath() {
|
|
15621
|
-
if (resolvedCliPath3 !== null &&
|
|
16049
|
+
if (resolvedCliPath3 !== null && existsSync11(resolvedCliPath3)) {
|
|
15622
16050
|
return resolvedCliPath3;
|
|
15623
16051
|
}
|
|
15624
16052
|
if (initPromise2) {
|
|
@@ -15626,7 +16054,7 @@ async function getAstGrepPath() {
|
|
|
15626
16054
|
}
|
|
15627
16055
|
initPromise2 = (async () => {
|
|
15628
16056
|
const syncPath = findSgCliPathSync();
|
|
15629
|
-
if (syncPath &&
|
|
16057
|
+
if (syncPath && existsSync11(syncPath)) {
|
|
15630
16058
|
resolvedCliPath3 = syncPath;
|
|
15631
16059
|
setSgCliPath(syncPath);
|
|
15632
16060
|
return syncPath;
|
|
@@ -15660,7 +16088,7 @@ async function runSg(options) {
|
|
|
15660
16088
|
const paths = options.paths && options.paths.length > 0 ? options.paths : ["."];
|
|
15661
16089
|
args.push(...paths);
|
|
15662
16090
|
let cliPath = getSgCliPath();
|
|
15663
|
-
if (!
|
|
16091
|
+
if (!existsSync11(cliPath) && cliPath !== "sg") {
|
|
15664
16092
|
const downloadedPath = await getAstGrepPath();
|
|
15665
16093
|
if (downloadedPath) {
|
|
15666
16094
|
cliPath = downloadedPath;
|
|
@@ -16101,12 +16529,12 @@ var ast_grep_transform = tool({
|
|
|
16101
16529
|
}
|
|
16102
16530
|
}
|
|
16103
16531
|
});
|
|
16104
|
-
// src/tools/
|
|
16532
|
+
// src/tools/grep/cli.ts
|
|
16105
16533
|
var {spawn: spawn6 } = globalThis.Bun;
|
|
16106
16534
|
|
|
16107
|
-
// src/tools/
|
|
16108
|
-
import { existsSync as
|
|
16109
|
-
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";
|
|
16110
16538
|
import { spawnSync } from "child_process";
|
|
16111
16539
|
var cachedCli = null;
|
|
16112
16540
|
function findExecutable(name) {
|
|
@@ -16123,17 +16551,17 @@ function findExecutable(name) {
|
|
|
16123
16551
|
}
|
|
16124
16552
|
function getOpenCodeBundledRg() {
|
|
16125
16553
|
const execPath = process.execPath;
|
|
16126
|
-
const execDir =
|
|
16554
|
+
const execDir = dirname4(execPath);
|
|
16127
16555
|
const isWindows = process.platform === "win32";
|
|
16128
16556
|
const rgName = isWindows ? "rg.exe" : "rg";
|
|
16129
16557
|
const candidates = [
|
|
16130
|
-
|
|
16131
|
-
|
|
16132
|
-
|
|
16133
|
-
|
|
16558
|
+
join11(execDir, rgName),
|
|
16559
|
+
join11(execDir, "bin", rgName),
|
|
16560
|
+
join11(execDir, "..", "bin", rgName),
|
|
16561
|
+
join11(execDir, "..", "libexec", rgName)
|
|
16134
16562
|
];
|
|
16135
16563
|
for (const candidate of candidates) {
|
|
16136
|
-
if (
|
|
16564
|
+
if (existsSync12(candidate)) {
|
|
16137
16565
|
return candidate;
|
|
16138
16566
|
}
|
|
16139
16567
|
}
|
|
@@ -16175,7 +16603,7 @@ var RG_SAFETY_FLAGS = [
|
|
|
16175
16603
|
];
|
|
16176
16604
|
var GREP_SAFETY_FLAGS = ["-n", "-H", "--color=never"];
|
|
16177
16605
|
|
|
16178
|
-
// src/tools/
|
|
16606
|
+
// src/tools/grep/cli.ts
|
|
16179
16607
|
function buildRgArgs(options) {
|
|
16180
16608
|
const args = [
|
|
16181
16609
|
...RG_SAFETY_FLAGS,
|
|
@@ -16319,7 +16747,7 @@ async function runRg(options) {
|
|
|
16319
16747
|
}
|
|
16320
16748
|
}
|
|
16321
16749
|
|
|
16322
|
-
// src/tools/
|
|
16750
|
+
// src/tools/grep/utils.ts
|
|
16323
16751
|
function formatGrepResult(result) {
|
|
16324
16752
|
if (result.error) {
|
|
16325
16753
|
return `Error: ${result.error}`;
|
|
@@ -16350,8 +16778,8 @@ function formatGrepResult(result) {
|
|
|
16350
16778
|
`);
|
|
16351
16779
|
}
|
|
16352
16780
|
|
|
16353
|
-
// src/tools/
|
|
16354
|
-
var
|
|
16781
|
+
// src/tools/grep/tools.ts
|
|
16782
|
+
var grep = tool({
|
|
16355
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.",
|
|
16356
16784
|
args: {
|
|
16357
16785
|
pattern: tool.schema.string().describe("The regex pattern to search for in file contents"),
|
|
@@ -16375,6 +16803,162 @@ var safe_grep = tool({
|
|
|
16375
16803
|
}
|
|
16376
16804
|
});
|
|
16377
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
|
+
|
|
16378
16962
|
// src/tools/index.ts
|
|
16379
16963
|
var builtinTools = {
|
|
16380
16964
|
lsp_hover,
|
|
@@ -16390,7 +16974,8 @@ var builtinTools = {
|
|
|
16390
16974
|
lsp_code_action_resolve,
|
|
16391
16975
|
ast_grep_search,
|
|
16392
16976
|
ast_grep_replace,
|
|
16393
|
-
|
|
16977
|
+
grep,
|
|
16978
|
+
glob
|
|
16394
16979
|
};
|
|
16395
16980
|
|
|
16396
16981
|
// src/mcp/websearch-exa.ts
|
|
@@ -16495,7 +17080,11 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
16495
17080
|
const todoContinuationEnforcer = createTodoContinuationEnforcer(ctx);
|
|
16496
17081
|
const contextWindowMonitor = createContextWindowMonitorHook(ctx);
|
|
16497
17082
|
const sessionRecovery = createSessionRecoveryHook(ctx);
|
|
17083
|
+
const pulseMonitor = createPulseMonitorHook(ctx);
|
|
16498
17084
|
const commentChecker = createCommentCheckerHooks();
|
|
17085
|
+
const grepOutputTruncator = createGrepOutputTruncatorHook(ctx);
|
|
17086
|
+
const directoryAgentsInjector = createDirectoryAgentsInjectorHook(ctx);
|
|
17087
|
+
const emptyTaskResponseDetector = createEmptyTaskResponseDetectorHook(ctx);
|
|
16499
17088
|
updateTerminalTitle({ sessionId: "main" });
|
|
16500
17089
|
const pluginConfig = loadPluginConfig(ctx.directory);
|
|
16501
17090
|
let mainSessionID;
|
|
@@ -16510,8 +17099,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
16510
17099
|
...agents
|
|
16511
17100
|
};
|
|
16512
17101
|
config3.tools = {
|
|
16513
|
-
...config3.tools
|
|
16514
|
-
grep: false
|
|
17102
|
+
...config3.tools
|
|
16515
17103
|
};
|
|
16516
17104
|
config3.mcp = {
|
|
16517
17105
|
...config3.mcp,
|
|
@@ -16521,6 +17109,8 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
16521
17109
|
event: async (input) => {
|
|
16522
17110
|
await todoContinuationEnforcer(input);
|
|
16523
17111
|
await contextWindowMonitor.event(input);
|
|
17112
|
+
await pulseMonitor.event(input);
|
|
17113
|
+
await directoryAgentsInjector.event(input);
|
|
16524
17114
|
const { event } = input;
|
|
16525
17115
|
const props = event.properties;
|
|
16526
17116
|
if (event.type === "session.created") {
|
|
@@ -16603,6 +17193,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
16603
17193
|
}
|
|
16604
17194
|
},
|
|
16605
17195
|
"tool.execute.before": async (input, output) => {
|
|
17196
|
+
await pulseMonitor["tool.execute.before"]();
|
|
16606
17197
|
await commentChecker["tool.execute.before"](input, output);
|
|
16607
17198
|
if (input.sessionID === mainSessionID) {
|
|
16608
17199
|
updateTerminalTitle({
|
|
@@ -16615,8 +17206,12 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
16615
17206
|
}
|
|
16616
17207
|
},
|
|
16617
17208
|
"tool.execute.after": async (input, output) => {
|
|
17209
|
+
await pulseMonitor["tool.execute.after"](input);
|
|
17210
|
+
await grepOutputTruncator["tool.execute.after"](input, output);
|
|
16618
17211
|
await contextWindowMonitor["tool.execute.after"](input, output);
|
|
16619
17212
|
await commentChecker["tool.execute.after"](input, output);
|
|
17213
|
+
await directoryAgentsInjector["tool.execute.after"](input, output);
|
|
17214
|
+
await emptyTaskResponseDetector["tool.execute.after"](input, output);
|
|
16620
17215
|
if (input.sessionID === mainSessionID) {
|
|
16621
17216
|
updateTerminalTitle({
|
|
16622
17217
|
sessionId: input.sessionID,
|