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.
Files changed (35) hide show
  1. package/README.ko.md +21 -5
  2. package/README.md +21 -5
  3. package/dist/hooks/directory-agents-injector/constants.d.ts +3 -0
  4. package/dist/hooks/directory-agents-injector/index.d.ts +22 -0
  5. package/dist/hooks/directory-agents-injector/storage.d.ts +3 -0
  6. package/dist/hooks/directory-agents-injector/types.d.ts +5 -0
  7. package/dist/hooks/empty-task-response-detector.d.ts +12 -0
  8. package/dist/hooks/grep-output-truncator.d.ts +12 -0
  9. package/dist/hooks/index.d.ts +4 -0
  10. package/dist/hooks/pulse-monitor.d.ts +10 -0
  11. package/dist/hooks/session-recovery/constants.d.ts +6 -0
  12. package/dist/hooks/session-recovery/index.d.ts +14 -0
  13. package/dist/hooks/session-recovery/storage.d.ts +14 -0
  14. package/dist/hooks/session-recovery/types.d.ts +75 -0
  15. package/dist/index.js +892 -297
  16. package/dist/tools/ast-grep/index.d.ts +8 -8
  17. package/dist/tools/ast-grep/tools.d.ts +12 -12
  18. package/dist/tools/glob/cli.d.ts +2 -0
  19. package/dist/tools/glob/constants.d.ts +6 -0
  20. package/dist/tools/glob/index.d.ts +2 -0
  21. package/dist/tools/glob/tools.d.ts +11 -0
  22. package/dist/tools/glob/types.d.ts +19 -0
  23. package/dist/tools/glob/utils.d.ts +2 -0
  24. package/dist/tools/grep/index.d.ts +2 -0
  25. package/dist/tools/{safe-grep → grep}/tools.d.ts +1 -1
  26. package/dist/tools/index.d.ts +20 -9
  27. package/dist/tools/lsp/client.d.ts +5 -1
  28. package/package.json +1 -1
  29. package/dist/hooks/grep-blocker.d.ts +0 -10
  30. package/dist/hooks/session-recovery.d.ts +0 -30
  31. package/dist/tools/safe-grep/index.d.ts +0 -2
  32. /package/dist/tools/{safe-grep → grep}/cli.d.ts +0 -0
  33. /package/dist/tools/{safe-grep → grep}/constants.d.ts +0 -0
  34. /package/dist/tools/{safe-grep → grep}/types.d.ts +0 -0
  35. /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 ENFORCEMENT]
605
+ var CONTINUATION_PROMPT = `[SYSTEM REMINDER - TODO CONTINUATION]
606
606
 
607
- Your todo list is NOT complete. There are still incomplete tasks remaining.
607
+ Incomplete tasks remain in your todo list. Continue working on the next pending task.
608
608
 
609
- CRITICAL INSTRUCTION:
610
- - You MUST NOT stop working until ALL todos are marked as completed
611
- - Continue working on the next pending task immediately
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} tasks remaining]`
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
- const projectHash = readdirSync(MESSAGE_STORAGE).find((dir) => {
931
- const sessionDir = join(MESSAGE_STORAGE, dir);
932
- try {
933
- return readdirSync(sessionDir).some((f) => f.includes(sessionID.replace("ses_", "")));
934
- } catch {
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 = join(MESSAGE_STORAGE, dir, sessionID);
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 readMessagesFromStorage(sessionID) {
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(join(messageDir, file), "utf-8");
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 readPartsFromStorage(messageID) {
967
- const partDir = join(PART_STORAGE, messageID);
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(join(partDir, file), "utf-8");
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 injectTextPartToStorage(sessionID, messageID, text) {
984
- const partDir = join(PART_STORAGE, messageID);
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(join(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
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 findEmptyContentMessageFromStorage(sessionID) {
1004
- const messages = readMessagesFromStorage(sessionID);
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
- const parts = readPartsFromStorage(msg.id);
1013
- const hasContent = parts.some((p) => {
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 null;
932
+ return emptyIds;
1031
933
  }
1032
- async function recoverEmptyContentMessage(_client, sessionID, failedAssistantMsg, _directory) {
1033
- const emptyMessageID = findEmptyContentMessageFromStorage(sessionID) || failedAssistantMsg.info?.id;
1034
- if (!emptyMessageID)
1035
- return false;
1036
- return injectTextPartToStorage(sessionID, emptyMessageID, "(interrupted)");
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
- async function fallbackRevertStrategy(client, sessionID, failedAssistantMsg, directory) {
1039
- const parentMsgID = failedAssistantMsg.info?.parentID;
1040
- const messagesResp = await client.session.messages({
1041
- path: { id: sessionID },
1042
- query: { directory }
1043
- });
1044
- const msgs = messagesResp.data;
1045
- if (!msgs || msgs.length === 0) {
1046
- return false;
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
- let targetUserMsg = null;
1049
- if (parentMsgID) {
1050
- targetUserMsg = msgs.find((m) => m.info?.id === parentMsgID) ?? null;
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
- if (!targetUserMsg) {
1053
- for (let i = msgs.length - 1;i >= 0; i--) {
1054
- if (msgs[i].info?.role === "user") {
1055
- targetUserMsg = msgs[i];
1056
- break;
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
- if (!targetUserMsg?.parts?.length) {
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
- await client.session.revert({
1064
- path: { id: sessionID },
1065
- body: { messageID: targetUserMsg.info?.id ?? "" },
1066
- query: { directory }
1067
- });
1068
- const textParts = targetUserMsg.parts.filter((p) => p.type === "text" && p.text).map((p) => ({ type: "text", text: p.text ?? "" }));
1069
- if (textParts.length === 0) {
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
- await client.session.prompt({
1073
- path: { id: sessionID },
1074
- body: { parts: textParts },
1075
- query: { directory }
1076
- });
1077
- return true;
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: "Deleting empty 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: toastTitle,
1132
- message: toastMessage,
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 join3 } from "path";
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 join2 } from "path";
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 || join2(homedir(), ".cache");
1193
- return join2(base, "oh-my-opencode", "bin");
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 = join2(getCacheDir(), getBinaryName());
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 = join2(cacheDir, binaryName);
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 = join2(cacheDir, assetName);
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
- unlinkSync(archivePath);
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 = join3(cliDir, "bin", binaryName);
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 = join3(pkgDir, "bin", binaryName);
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 existsSync5, readFileSync as readFileSync2 } from "fs";
1781
- import { join as join4 } from "path";
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 (!existsSync5(path2))
2191
+ if (!existsSync7(path2))
1785
2192
  return null;
1786
2193
  try {
1787
- return JSON.parse(readFileSync2(path2, "utf-8"));
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: join4(cwd, ".opencode", "oh-my-opencode.json"),
1796
- user: join4(homedir2(), ".config", "opencode", "oh-my-opencode.json"),
1797
- opencode: join4(homedir2(), ".config", "opencode", "opencode.json")
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 (existsSync5(join4(p, cmd))) {
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 readFileSync3 } from "fs";
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((resolve2) => setTimeout(resolve2, 100));
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 ("id" in msg && "method" in msg) {
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((resolve2, reject) => {
2224
- this.pending.set(id, { resolve: resolve2, reject });
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, _params) {
2668
+ handleServerRequest(id, method, params) {
2257
2669
  if (method === "workspace/configuration") {
2258
- this.respond(id, [{}]);
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", { settings: {} });
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 = resolve(filePath);
2744
+ const absPath = resolve2(filePath);
2325
2745
  if (this.openedFiles.has(absPath))
2326
2746
  return;
2327
- const text = readFileSync3(absPath, "utf-8");
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 = resolve(filePath);
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 = resolve(filePath);
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 = resolve(filePath);
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 = resolve(filePath);
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 = resolve(filePath);
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
- return this.send("textDocument/diagnostic", {
2381
- textDocument: { uri: `file://${absPath}` }
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 = resolve(filePath);
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 = resolve(filePath);
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 = resolve(filePath);
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 resolve2 } from "path";
2434
- import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
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 = resolve2(filePath);
2437
- if (!existsSync6(dir) || !__require("fs").statSync(dir).isDirectory()) {
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 (existsSync6(__require("path").join(dir, marker))) {
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(resolve2(filePath));
2877
+ return __require("path").dirname(resolve3(filePath));
2450
2878
  }
2451
2879
  async function withLspClient(filePath, fn) {
2452
- const absPath = resolve2(filePath);
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 = readFileSync4(filePath, "utf-8");
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
- writeFileSync2(filePath, lines.join(`
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
- writeFileSync2(filePath, "", "utf-8");
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 = readFileSync4(oldPath, "utf-8");
2668
- writeFileSync2(newPath, content, "utf-8");
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 dirname2, join as join6 } from "path";
15369
- import { existsSync as existsSync8, statSync } from "fs";
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 existsSync7, mkdirSync as mkdirSync3, chmodSync as chmodSync2, unlinkSync as unlinkSync2 } from "fs";
15374
- import { join as join5 } from "path";
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 || join5(homedir3(), "AppData", "Local");
15401
- return join5(base2, "oh-my-opencode", "bin");
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 || join5(homedir3(), ".cache");
15405
- return join5(base, "oh-my-opencode", "bin");
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 = join5(getCacheDir2(), getBinaryName3());
15412
- return existsSync7(binaryPath) ? binaryPath : null;
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 = join5(cacheDir, binaryName);
15439
- if (existsSync7(binaryPath)) {
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 (!existsSync7(cacheDir)) {
15448
- mkdirSync3(cacheDir, { recursive: true });
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 = join5(cacheDir, assetName);
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 (existsSync7(archivePath)) {
15459
- unlinkSync2(archivePath);
15886
+ if (existsSync9(archivePath)) {
15887
+ unlinkSync4(archivePath);
15460
15888
  }
15461
- if (process.platform !== "win32" && existsSync7(binaryPath)) {
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 = dirname2(cliPkgPath);
15512
- const sgPath = join6(cliDir, binaryName);
15513
- if (existsSync8(sgPath) && isValidBinary(sgPath)) {
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 = dirname2(pkgPath);
15950
+ const pkgDir = dirname3(pkgPath);
15523
15951
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
15524
- const binaryPath = join6(pkgDir, astGrepName);
15525
- if (existsSync8(binaryPath) && isValidBinary(binaryPath)) {
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 (existsSync8(path2) && isValidBinary(path2)) {
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 existsSync9 } from "fs";
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 && existsSync9(resolvedCliPath3)) {
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 && existsSync9(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 (!existsSync9(cliPath) && cliPath !== "sg") {
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/safe-grep/cli.ts
16532
+ // src/tools/grep/cli.ts
16105
16533
  var {spawn: spawn6 } = globalThis.Bun;
16106
16534
 
16107
- // src/tools/safe-grep/constants.ts
16108
- import { existsSync as existsSync10 } from "fs";
16109
- import { join as join7, dirname as dirname3 } from "path";
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 = dirname3(execPath);
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
- join7(execDir, rgName),
16131
- join7(execDir, "bin", rgName),
16132
- join7(execDir, "..", "bin", rgName),
16133
- join7(execDir, "..", "libexec", rgName)
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 (existsSync10(candidate)) {
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/safe-grep/cli.ts
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/safe-grep/utils.ts
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/safe-grep/tools.ts
16354
- var safe_grep = tool({
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
- safe_grep
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,