oh-my-opencode 0.1.26 → 0.1.28

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