@morphllm/morphsdk 0.2.56 → 0.2.58

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 (165) hide show
  1. package/dist/anthropic-CaFUHxBW.d.ts +89 -0
  2. package/dist/{chunk-SALJ2K6S.js → chunk-2CASO3ZO.js} +60 -98
  3. package/dist/chunk-2CASO3ZO.js.map +1 -0
  4. package/dist/chunk-374N3GIA.js +118 -0
  5. package/dist/chunk-374N3GIA.js.map +1 -0
  6. package/dist/chunk-3IQIT6MC.js +65 -0
  7. package/dist/chunk-3IQIT6MC.js.map +1 -0
  8. package/dist/chunk-4VGOBA2J.js +57 -0
  9. package/dist/chunk-4VGOBA2J.js.map +1 -0
  10. package/dist/chunk-527P5X2E.js +98 -0
  11. package/dist/chunk-527P5X2E.js.map +1 -0
  12. package/dist/chunk-6N6ZYZYD.js +74 -0
  13. package/dist/chunk-6N6ZYZYD.js.map +1 -0
  14. package/dist/chunk-6Y5JB4JC.js +195 -0
  15. package/dist/chunk-6Y5JB4JC.js.map +1 -0
  16. package/dist/{chunk-WSSSSBWU.js → chunk-7EIHYJSG.js} +18 -18
  17. package/dist/chunk-7EIHYJSG.js.map +1 -0
  18. package/dist/{chunk-TICMYDII.js → chunk-APP75CBN.js} +33 -16
  19. package/dist/chunk-APP75CBN.js.map +1 -0
  20. package/dist/chunk-ILJ3J5IA.js +72 -0
  21. package/dist/chunk-ILJ3J5IA.js.map +1 -0
  22. package/dist/chunk-ISWL67SF.js +1 -0
  23. package/dist/chunk-KW7OEGZK.js +9 -0
  24. package/dist/chunk-KW7OEGZK.js.map +1 -0
  25. package/dist/chunk-Q5AHGIQO.js +205 -0
  26. package/dist/chunk-Q5AHGIQO.js.map +1 -0
  27. package/dist/{chunk-TJIUA27P.js → chunk-XT5ZO6ES.js} +9 -5
  28. package/dist/chunk-XT5ZO6ES.js.map +1 -0
  29. package/dist/{chunk-LVPVVLTI.js → chunk-YV75OQTE.js} +105 -17
  30. package/dist/chunk-YV75OQTE.js.map +1 -0
  31. package/dist/{chunk-ZJIIICRA.js → chunk-ZO4PPFCZ.js} +60 -29
  32. package/dist/chunk-ZO4PPFCZ.js.map +1 -0
  33. package/dist/{client-CFoR--IU.d.ts → client-CextMMm9.d.ts} +10 -15
  34. package/dist/client.cjs +698 -466
  35. package/dist/client.cjs.map +1 -1
  36. package/dist/client.d.ts +3 -2
  37. package/dist/client.js +14 -15
  38. package/dist/finish-kXAcUJyB.d.ts +33 -0
  39. package/dist/gemini-CE80Pbdy.d.ts +117 -0
  40. package/dist/index.cjs +711 -466
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.ts +4 -3
  43. package/dist/index.js +16 -16
  44. package/dist/openai-Fvpqln7F.d.ts +89 -0
  45. package/dist/tools/warp_grep/agent/config.cjs +8 -4
  46. package/dist/tools/warp_grep/agent/config.cjs.map +1 -1
  47. package/dist/tools/warp_grep/agent/config.d.ts +7 -2
  48. package/dist/tools/warp_grep/agent/config.js +1 -1
  49. package/dist/tools/warp_grep/agent/formatter.cjs +32 -15
  50. package/dist/tools/warp_grep/agent/formatter.cjs.map +1 -1
  51. package/dist/tools/warp_grep/agent/formatter.d.ts +1 -1
  52. package/dist/tools/warp_grep/agent/formatter.js +1 -1
  53. package/dist/tools/warp_grep/agent/parser.cjs +104 -17
  54. package/dist/tools/warp_grep/agent/parser.cjs.map +1 -1
  55. package/dist/tools/warp_grep/agent/parser.d.ts +3 -5
  56. package/dist/tools/warp_grep/agent/parser.js +1 -3
  57. package/dist/tools/warp_grep/agent/prompt.cjs +132 -56
  58. package/dist/tools/warp_grep/agent/prompt.cjs.map +1 -1
  59. package/dist/tools/warp_grep/agent/prompt.d.ts +1 -1
  60. package/dist/tools/warp_grep/agent/prompt.js +1 -1
  61. package/dist/tools/warp_grep/agent/runner.cjs +466 -313
  62. package/dist/tools/warp_grep/agent/runner.cjs.map +1 -1
  63. package/dist/tools/warp_grep/agent/runner.d.ts +1 -0
  64. package/dist/tools/warp_grep/agent/runner.js +6 -9
  65. package/dist/tools/warp_grep/agent/types.cjs.map +1 -1
  66. package/dist/tools/warp_grep/agent/types.d.ts +9 -2
  67. package/dist/tools/warp_grep/anthropic.cjs +656 -380
  68. package/dist/tools/warp_grep/anthropic.cjs.map +1 -1
  69. package/dist/tools/warp_grep/anthropic.d.ts +4 -74
  70. package/dist/tools/warp_grep/anthropic.js +13 -16
  71. package/dist/tools/warp_grep/client.cjs +1593 -0
  72. package/dist/tools/warp_grep/client.cjs.map +1 -0
  73. package/dist/tools/warp_grep/client.d.ts +87 -0
  74. package/dist/tools/warp_grep/client.js +26 -0
  75. package/dist/tools/warp_grep/gemini.cjs +1587 -0
  76. package/dist/tools/warp_grep/gemini.cjs.map +1 -0
  77. package/dist/tools/warp_grep/gemini.d.ts +7 -0
  78. package/dist/tools/warp_grep/gemini.js +34 -0
  79. package/dist/tools/warp_grep/harness.cjs +1195 -0
  80. package/dist/tools/warp_grep/harness.cjs.map +1 -0
  81. package/dist/tools/warp_grep/harness.d.ts +107 -0
  82. package/dist/tools/warp_grep/harness.js +68 -0
  83. package/dist/tools/warp_grep/harness.js.map +1 -0
  84. package/dist/tools/warp_grep/index.cjs +818 -466
  85. package/dist/tools/warp_grep/index.cjs.map +1 -1
  86. package/dist/tools/warp_grep/index.d.ts +11 -6
  87. package/dist/tools/warp_grep/index.js +43 -23
  88. package/dist/tools/warp_grep/openai.cjs +656 -378
  89. package/dist/tools/warp_grep/openai.cjs.map +1 -1
  90. package/dist/tools/warp_grep/openai.d.ts +4 -74
  91. package/dist/tools/warp_grep/openai.js +13 -14
  92. package/dist/tools/warp_grep/providers/local.cjs +66 -27
  93. package/dist/tools/warp_grep/providers/local.cjs.map +1 -1
  94. package/dist/tools/warp_grep/providers/local.d.ts +4 -9
  95. package/dist/tools/warp_grep/providers/local.js +2 -2
  96. package/dist/tools/warp_grep/providers/remote.cjs +211 -0
  97. package/dist/tools/warp_grep/providers/remote.cjs.map +1 -0
  98. package/dist/tools/warp_grep/providers/remote.d.ts +67 -0
  99. package/dist/tools/warp_grep/providers/remote.js +9 -0
  100. package/dist/tools/warp_grep/providers/types.cjs.map +1 -1
  101. package/dist/tools/warp_grep/providers/types.d.ts +7 -15
  102. package/dist/tools/warp_grep/vercel.cjs +668 -397
  103. package/dist/tools/warp_grep/vercel.cjs.map +1 -1
  104. package/dist/tools/warp_grep/vercel.d.ts +4 -51
  105. package/dist/tools/warp_grep/vercel.js +16 -15
  106. package/dist/types-a_hxdPI6.d.ts +144 -0
  107. package/dist/vercel-3yjvfmVB.d.ts +66 -0
  108. package/package.json +17 -2
  109. package/dist/chunk-4ZHDBKBY.js +0 -83
  110. package/dist/chunk-4ZHDBKBY.js.map +0 -1
  111. package/dist/chunk-73RQWOQC.js +0 -16
  112. package/dist/chunk-73RQWOQC.js.map +0 -1
  113. package/dist/chunk-7OQOOB3R.js +0 -1
  114. package/dist/chunk-EK7OQPWD.js +0 -44
  115. package/dist/chunk-EK7OQPWD.js.map +0 -1
  116. package/dist/chunk-GJURLQ3L.js +0 -77
  117. package/dist/chunk-GJURLQ3L.js.map +0 -1
  118. package/dist/chunk-HQO45BAJ.js +0 -14
  119. package/dist/chunk-HQO45BAJ.js.map +0 -1
  120. package/dist/chunk-LVPVVLTI.js.map +0 -1
  121. package/dist/chunk-NDZO5IPV.js +0 -121
  122. package/dist/chunk-NDZO5IPV.js.map +0 -1
  123. package/dist/chunk-QVRXBAMM.js +0 -107
  124. package/dist/chunk-QVRXBAMM.js.map +0 -1
  125. package/dist/chunk-SALJ2K6S.js.map +0 -1
  126. package/dist/chunk-TICMYDII.js.map +0 -1
  127. package/dist/chunk-TJIUA27P.js.map +0 -1
  128. package/dist/chunk-UIRJE422.js +0 -70
  129. package/dist/chunk-UIRJE422.js.map +0 -1
  130. package/dist/chunk-WETRQJGU.js +0 -129
  131. package/dist/chunk-WETRQJGU.js.map +0 -1
  132. package/dist/chunk-WSSSSBWU.js.map +0 -1
  133. package/dist/chunk-ZJIIICRA.js.map +0 -1
  134. package/dist/core-CpkYEi_T.d.ts +0 -158
  135. package/dist/tools/warp_grep/agent/grep_helpers.cjs +0 -148
  136. package/dist/tools/warp_grep/agent/grep_helpers.cjs.map +0 -1
  137. package/dist/tools/warp_grep/agent/grep_helpers.d.ts +0 -16
  138. package/dist/tools/warp_grep/agent/grep_helpers.js +0 -14
  139. package/dist/tools/warp_grep/tools/analyse.cjs +0 -40
  140. package/dist/tools/warp_grep/tools/analyse.cjs.map +0 -1
  141. package/dist/tools/warp_grep/tools/analyse.d.ts +0 -10
  142. package/dist/tools/warp_grep/tools/analyse.js +0 -8
  143. package/dist/tools/warp_grep/tools/finish.cjs +0 -69
  144. package/dist/tools/warp_grep/tools/finish.cjs.map +0 -1
  145. package/dist/tools/warp_grep/tools/finish.d.ts +0 -10
  146. package/dist/tools/warp_grep/tools/finish.js +0 -10
  147. package/dist/tools/warp_grep/tools/grep.cjs +0 -38
  148. package/dist/tools/warp_grep/tools/grep.cjs.map +0 -1
  149. package/dist/tools/warp_grep/tools/grep.d.ts +0 -8
  150. package/dist/tools/warp_grep/tools/grep.js +0 -15
  151. package/dist/tools/warp_grep/tools/grep.js.map +0 -1
  152. package/dist/tools/warp_grep/tools/read.cjs +0 -38
  153. package/dist/tools/warp_grep/tools/read.cjs.map +0 -1
  154. package/dist/tools/warp_grep/tools/read.d.ts +0 -9
  155. package/dist/tools/warp_grep/tools/read.js +0 -8
  156. package/dist/tools/warp_grep/tools/read.js.map +0 -1
  157. package/dist/tools/warp_grep/utils/format.cjs +0 -42
  158. package/dist/tools/warp_grep/utils/format.cjs.map +0 -1
  159. package/dist/tools/warp_grep/utils/format.d.ts +0 -4
  160. package/dist/tools/warp_grep/utils/format.js +0 -18
  161. package/dist/tools/warp_grep/utils/format.js.map +0 -1
  162. /package/dist/{chunk-7OQOOB3R.js.map → chunk-ISWL67SF.js.map} +0 -0
  163. /package/dist/tools/warp_grep/{agent/grep_helpers.js.map → client.js.map} +0 -0
  164. /package/dist/tools/warp_grep/{tools/analyse.js.map → gemini.js.map} +0 -0
  165. /package/dist/tools/warp_grep/{tools/finish.js.map → providers/remote.js.map} +0 -0
package/dist/index.cjs CHANGED
@@ -961,9 +961,13 @@ async function checkHealth(config = {}) {
961
961
 
962
962
  // tools/warp_grep/agent/config.ts
963
963
  var AGENT_CONFIG = {
964
- // Give the model freedom; failsafe cap to prevent infinite loops
965
- MAX_ROUNDS: 10,
966
- TIMEOUT_MS: 3e4
964
+ MAX_TURNS: 4,
965
+ TIMEOUT_MS: 3e4,
966
+ MAX_CONTEXT_CHARS: 54e4,
967
+ MAX_OUTPUT_LINES: 200,
968
+ MAX_READ_LINES: 800,
969
+ MAX_LIST_DEPTH: 3,
970
+ LIST_TIMEOUT_MS: 2e3
967
971
  };
968
972
  var BUILTIN_EXCLUDES = [
969
973
  // Version control
@@ -1045,113 +1049,191 @@ var BUILTIN_EXCLUDES = [
1045
1049
  ".*"
1046
1050
  ];
1047
1051
  var DEFAULT_EXCLUDES = (process.env.MORPH_WARP_GREP_EXCLUDE || "").split(",").map((s) => s.trim()).filter(Boolean).concat(BUILTIN_EXCLUDES);
1048
- var DEFAULT_MODEL = "morph-warp-grep";
1052
+ var DEFAULT_MODEL = "morph-warp-grep-v1";
1049
1053
 
1050
1054
  // tools/warp_grep/agent/prompt.ts
1051
- var SYSTEM_PROMPT = `You are a code search agent. Your task is to find all relevant code for a given query.
1055
+ var SYSTEM_PROMPT = `You are a code search agent. Your task is to find all relevant code for a given search_string.
1052
1056
 
1053
- <workflow>
1057
+ ### workflow
1054
1058
  You have exactly 4 turns. The 4th turn MUST be a \`finish\` call. Each turn allows up to 8 parallel tool calls.
1055
1059
 
1056
- - Turn 1: Map the territory OR dive deep (based on query specificity)
1060
+ - Turn 1: Map the territory OR dive deep (based on search_string specificity)
1057
1061
  - Turn 2-3: Refine based on findings
1058
1062
  - Turn 4: MUST call \`finish\` with all relevant code locations
1059
1063
  - You MAY call \`finish\` early if confident\u2014but never before at least 1 search turn.
1064
+ - The user strongly prefers if you can call the finish tool early, but you must be correct
1060
1065
 
1061
- Remember, if the task feels easy to you, it is strongly desirable to call \`finish\` early using fewer turns, but quality over speed.
1062
- </workflow>
1066
+ Remember, if the task feels easy to you, it is strongly desirable to call 'finish' early using fewer turns, but quality over speed
1063
1067
 
1064
- <tools>
1065
- ### \`analyse <path> [pattern]\`
1066
- Directory tree or file search. Shows structure of a path, optionally filtered by regex pattern.
1067
- - \`path\`: Required. Directory or file path (use \`.\` for repo root)
1068
- - \`pattern\`: Optional regex to filter results
1068
+ ### tools
1069
+ Tool calls use nested XML elements:
1070
+ \`\`\`xml
1071
+ <tool_name>
1072
+ <parameter>value</parameter>
1073
+ </tool_name>
1074
+ \`\`\`
1075
+
1076
+ ### \`list_directory\`
1077
+ Directory tree view. Shows structure of a path, optionally filtered by regex pattern.
1078
+
1079
+ Elements:
1080
+ - \`<path>\` (required): Directory path to list (use \`.\` for repo root)
1081
+ - \`<pattern>\` (optional): Regex to filter results
1069
1082
 
1070
1083
  Examples:
1071
1084
  \`\`\`
1072
- analyse .
1073
- analyse src/api
1074
- analyse . ".*\\.ts$"
1075
- analyse src "test.*"
1085
+ <list_directory>
1086
+ <path>src/services</path>
1087
+ </list_directory>
1088
+
1089
+ <list_directory>
1090
+ <path>lib/utils</path>
1091
+ <pattern>.*\\.(ts|js)$</pattern>
1092
+ </list_directory>
1076
1093
  \`\`\`
1077
1094
 
1078
- ### \`read <path>[:start-end]\`
1079
- Read file contents. Line range is 1-based, inclusive.
1095
+ ### \`read\`
1096
+ Read file contents. Supports multiple line ranges.
1080
1097
  - Returns numbered lines for easy reference
1081
- - Omit range to read entire file
1098
+ - ALWAYS include import statements (usually lines 1-20). Better to over-include than miss context.
1099
+
1100
+ Elements:
1101
+ - \`<path>\` (required): File path to read
1102
+ - \`<lines>\` (optional): Line ranges like "1-50,75-80,100-120" (omit to read entire file)
1082
1103
 
1083
1104
  Examples:
1084
1105
  \`\`\`
1085
- read src/main.py
1086
- read src/db/conn.py:10-50
1087
- read package.json:1-20
1106
+ <read>
1107
+ <path>src/main.py</path>
1108
+ </read>
1109
+
1110
+ <read>
1111
+ <path>src/auth.py</path>
1112
+ <lines>1-20,45-80,150-200</lines>
1113
+ </read>
1088
1114
  \`\`\`
1089
1115
 
1090
- ### \`grep '<pattern>' <path>\`
1091
- Ripgrep search. Finds pattern matches across files.
1092
- - \`'<pattern>'\`: Required. Regex pattern wrapped in single quotes
1093
- - \`<path>\`: Required. Directory or file to search (use \`.\` for repo root)
1116
+ ### \`grep\`
1117
+ Search for pattern matches across files. Returns matches with 1 line of context above and below.
1118
+ - Match lines use \`:\` separator \u2192 \`filepath:linenum:content\`
1119
+ - Context lines use \`-\` separator \u2192 \`filepath-linenum-content\`
1120
+
1121
+ Elements:
1122
+ - \`<pattern>\` (required): Search pattern (regex). Use \`(a|b)\` for OR patterns.
1123
+ - \`<sub_dir>\` (optional): Subdirectory to search in (defaults to \`.\`)
1124
+ - \`<glob>\` (optional): File pattern filter like \`*.py\` or \`*.{ts,tsx}\`
1094
1125
 
1095
1126
  Examples:
1096
1127
  \`\`\`
1097
- grep 'class.*Service' src/
1098
- grep 'def authenticate' .
1099
- grep 'import.*from' src/components/
1100
- grep 'TODO' .
1128
+ <grep>
1129
+ <pattern>(authenticate|authorize|login)</pattern>
1130
+ <sub_dir>src/auth/</sub_dir>
1131
+ </grep>
1132
+
1133
+ <grep>
1134
+ <pattern>class.*(Service|Controller)</pattern>
1135
+ <glob>*.{ts,js}</glob>
1136
+ </grep>
1137
+
1138
+ <grep>
1139
+ <pattern>(DB_HOST|DATABASE_URL|connection)</pattern>
1140
+ <glob>*.{py,yaml,env}</glob>
1141
+ <sub_dir>lib/</sub_dir>
1142
+ </grep>
1101
1143
  \`\`\`
1102
1144
 
1103
- ### \`finish <file1:ranges> [file2:ranges ...]\`
1104
- Submit final answer with all relevant code locations.
1105
- - Include generous line ranges\u2014don't be stingy with context
1106
- - Ranges are comma-separated: \`file.py:10-30,50-60\`
1107
- - ALWAYS include import statements at the top of files (usually lines 1-20)
1108
- - If code spans multiple files, include ALL of them
1109
- - Small files can be returned in full
1145
+ ### \`finish\`
1146
+ Submit final answer with all relevant code locations. Uses nested \`<file>\` elements.
1147
+
1148
+ File elements:
1149
+ - \`<path>\` (required): File path
1150
+ - \`<lines>\` (optional): Line ranges like "1-50,75-80" (\`*\` for entire file)
1151
+
1152
+ ALWAYS include import statements (usually lines 1-20). Better to over-include than miss context.
1110
1153
 
1111
1154
  Examples:
1112
1155
  \`\`\`
1113
- finish src/auth.py:1-15,25-50,75-80 src/models/user.py:1-10,20-45
1114
- finish src/index.ts:1-100
1156
+ <finish>
1157
+ <file>
1158
+ <path>src/auth.py</path>
1159
+ <lines>1-15,25-50,75-80</lines>
1160
+ </file>
1161
+ <file>
1162
+ <path>src/models/user.py</path>
1163
+ <lines>*</lines>
1164
+ </file>
1165
+ </finish>
1115
1166
  \`\`\`
1116
1167
  </tools>
1117
1168
 
1118
1169
  <strategy>
1119
- **Before your first tool call, classify the query:**
1170
+ **Before your first tool call, classify the search_string:**
1120
1171
 
1121
- | Query Type | Turn 1 Strategy | Early Finish? |
1122
- |------------|-----------------|---------------|
1123
- | **Specific** (function name, error string, unique identifier) | 8 parallel greps on likely paths | Often by turn 2 |
1124
- | **Conceptual** (how does X work, where is Y handled) | analyse + 2-3 broad greps | Rarely early |
1125
- | **Exploratory** (find all tests, list API endpoints) | analyse at multiple depths | Usually needs 3 turns |
1172
+ | Search_string Type | Round 1 Strategy | Early Finish? |
1173
+ |------------|------------------|---------------|
1174
+ | **Specific** (function name, error string, unique identifier) | 8 parallel greps on likely paths | Often by round 2 |
1175
+ | **Conceptual** (how does X work, where is Y handled) | list_directory + 2-3 broad greps | Rarely early |
1176
+ | **Exploratory** (find all tests, list API endpoints) | list_directory at multiple depths | Usually needs 3 rounds |
1126
1177
 
1127
1178
  **Parallel call patterns:**
1128
1179
  - **Shotgun grep**: Same pattern, 8 different directories\u2014fast coverage
1129
1180
  - **Variant grep**: 8 pattern variations (synonyms, naming conventions)\u2014catches inconsistent codebases
1130
- - **Funnel**: 1 analyse + 7 greps\u2014orient and search simultaneously
1181
+ - **Funnel**: 1 list_directory + 7 greps\u2014orient and search simultaneously
1131
1182
  - **Deep read**: 8 reads on files you already identified\u2014gather full context fast
1183
+
1184
+ **Tool call expectations:**
1185
+ - Low quality tool calls are ones that give back sparse information. This either means they are not well thought out and are not educated guesses OR, they are too broad and give back too many results.
1186
+ - High quality tool calls strike a balance between complexity in the tool call to exclude results we know we don't want, and how wide the search space is so that we don't miss anything. It is ok to start off with wider search spaces, but is imperative that you use your intuition from there on out and seek high quality tool calls only.
1187
+ - You are not starting blind, you have some information about root level repo structure going in, so use that to prevent making trivial repo wide queries.
1188
+ - The grep tool shows you which file path and line numbers the pattern was found in, use this information smartly when trying to read the file.
1132
1189
  </strategy>
1133
1190
 
1134
1191
  <output_format>
1135
1192
  EVERY response MUST follow this exact format:
1136
1193
 
1137
1194
  1. First, wrap your reasoning in \`<think>...</think>\` tags containing:
1138
- - Query classification (specific/conceptual/exploratory)
1139
- - Confidence estimate (can I finish in 1-2 turns?)
1140
- - This turn's parallel strategy
1195
+ - Search_string classification (specific/conceptual/exploratory)
1196
+ - Confidence estimate (can I finish in 1-2 rounds?)
1197
+ - This round's parallel strategy
1141
1198
  - What signals would let me finish early?
1142
1199
 
1143
- 2. Then, output tool calls wrapped in \`<tool_call>...</tool_call>\` tags, one per line.
1200
+ 2. Then, output up to 8 tool calls using nested XML elements.
1144
1201
 
1145
1202
  Example:
1146
1203
  \`\`\`
1147
1204
  <think>
1148
- This is a specific query about authentication. I'll grep for auth-related patterns.
1149
- High confidence I can finish in 2 turns if I find the auth module.
1205
+ This is a specific search_string about authentication. I'll grep for auth-related patterns.
1206
+ High confidence I can finish in 2 rounds if I find the auth module. I have already been shown the repo's structure at root
1150
1207
  Strategy: Shotgun grep across likely directories.
1151
1208
  </think>
1152
- <tool_call>grep 'authenticate' src/</tool_call>
1153
- <tool_call>grep 'login' src/</tool_call>
1154
- <tool_call>analyse src/auth</tool_call>
1209
+ <grep>
1210
+ <pattern>(authenticate|login|session)</pattern>
1211
+ <sub_dir>src/auth/</sub_dir>
1212
+ </grep>
1213
+ <grep>
1214
+ <pattern>(middleware|interceptor)</pattern>
1215
+ <glob>*.{ts,js}</glob>
1216
+ </grep>
1217
+ <list_directory>
1218
+ <path>src/auth</path>
1219
+ </list_directory>
1220
+ \`\`\`
1221
+
1222
+ Finishing example:
1223
+ \`\`\`
1224
+ <think>
1225
+ I think I have a rough idea, but this is my last turn so I must call the finish tool regardless.
1226
+ </think>
1227
+ <finish>
1228
+ <file>
1229
+ <path>src/auth/login.py</path>
1230
+ <lines>1-50</lines>
1231
+ </file>
1232
+ <file>
1233
+ <path>src/middleware/session.py</path>
1234
+ <lines>10-80</lines>
1235
+ </file>
1236
+ </finish>
1155
1237
  \`\`\`
1156
1238
 
1157
1239
  No commentary outside \`<think>\`. No explanations after tool calls.
@@ -1164,17 +1246,111 @@ When calling \`finish\`:
1164
1246
  - Include any type definitions, interfaces, or constants used
1165
1247
  - Better to over-include than leave the user missing context
1166
1248
  - If unsure about boundaries, include more rather than less
1167
- </finishing_requirements>
1168
-
1169
- Begin your exploration now to find code relevant to the query.`;
1249
+ </finishing_requirements>`;
1170
1250
  function getSystemPrompt() {
1171
1251
  return SYSTEM_PROMPT;
1172
1252
  }
1173
1253
 
1174
1254
  // tools/warp_grep/agent/parser.ts
1175
- var VALID_COMMANDS = ["analyse", "grep", "read", "finish"];
1255
+ var VALID_COMMANDS = ["list_directory", "grep", "read", "finish"];
1256
+ function isValidCommand(name) {
1257
+ return VALID_COMMANDS.includes(name);
1258
+ }
1259
+ function getXmlElementText(xml, tagName) {
1260
+ const regex = new RegExp(`<${tagName}>([\\s\\S]*?)</${tagName}>`, "i");
1261
+ const match = xml.match(regex);
1262
+ return match ? match[1].trim() : null;
1263
+ }
1264
+ function parseNestedXmlTools(text) {
1265
+ const tools = [];
1266
+ const toolRegex = /<([a-z_][a-z0-9_]*)>([\s\S]*?)<\/\1>/gi;
1267
+ let match;
1268
+ while ((match = toolRegex.exec(text)) !== null) {
1269
+ const rawToolName = match[1].toLowerCase();
1270
+ const content = match[2];
1271
+ if (!isValidCommand(rawToolName)) continue;
1272
+ const toolName = rawToolName;
1273
+ if (toolName === "list_directory") {
1274
+ const path5 = getXmlElementText(content, "path");
1275
+ const pattern = getXmlElementText(content, "pattern");
1276
+ if (path5) {
1277
+ tools.push({ name: "list_directory", arguments: { path: path5, pattern } });
1278
+ }
1279
+ } else if (toolName === "grep") {
1280
+ const pattern = getXmlElementText(content, "pattern");
1281
+ const subDir = getXmlElementText(content, "sub_dir");
1282
+ const glob = getXmlElementText(content, "glob");
1283
+ if (pattern) {
1284
+ tools.push({
1285
+ name: "grep",
1286
+ arguments: {
1287
+ pattern,
1288
+ path: subDir || ".",
1289
+ ...glob && { glob }
1290
+ }
1291
+ });
1292
+ }
1293
+ } else if (toolName === "read") {
1294
+ const path5 = getXmlElementText(content, "path");
1295
+ const linesStr = getXmlElementText(content, "lines");
1296
+ if (path5) {
1297
+ const args = { path: path5 };
1298
+ if (linesStr) {
1299
+ const ranges = [];
1300
+ for (const rangeStr of linesStr.split(",")) {
1301
+ const trimmed = rangeStr.trim();
1302
+ if (!trimmed) continue;
1303
+ const [s, e] = trimmed.split("-").map((v) => parseInt(v.trim(), 10));
1304
+ if (Number.isFinite(s) && Number.isFinite(e)) {
1305
+ ranges.push([s, e]);
1306
+ } else if (Number.isFinite(s)) {
1307
+ ranges.push([s, s]);
1308
+ }
1309
+ }
1310
+ if (ranges.length === 1) {
1311
+ args.start = ranges[0][0];
1312
+ args.end = ranges[0][1];
1313
+ } else if (ranges.length > 1) {
1314
+ args.lines = ranges;
1315
+ }
1316
+ }
1317
+ tools.push({ name: "read", arguments: args });
1318
+ }
1319
+ } else if (toolName === "finish") {
1320
+ const fileRegex = /<file>([\s\S]*?)<\/file>/gi;
1321
+ const files = [];
1322
+ let fileMatch;
1323
+ while ((fileMatch = fileRegex.exec(content)) !== null) {
1324
+ const fileContent = fileMatch[1];
1325
+ const filePath = getXmlElementText(fileContent, "path");
1326
+ const linesStr = getXmlElementText(fileContent, "lines");
1327
+ if (filePath && linesStr) {
1328
+ const ranges = [];
1329
+ for (const rangeStr of linesStr.split(",")) {
1330
+ if (rangeStr.trim() === "*") {
1331
+ ranges.push([1, 999999]);
1332
+ } else {
1333
+ const [s, e] = rangeStr.split("-").map((v) => parseInt(v.trim(), 10));
1334
+ if (Number.isFinite(s) && Number.isFinite(e)) {
1335
+ ranges.push([s, e]);
1336
+ }
1337
+ }
1338
+ }
1339
+ if (ranges.length > 0) {
1340
+ files.push({ path: filePath, lines: ranges });
1341
+ }
1342
+ }
1343
+ }
1344
+ if (files.length > 0) {
1345
+ tools.push({ name: "finish", arguments: { files } });
1346
+ }
1347
+ }
1348
+ }
1349
+ return tools;
1350
+ }
1176
1351
  function preprocessText(text) {
1177
1352
  let processed = text.replace(/<think>[\s\S]*?<\/think>/gi, "");
1353
+ const nestedTools = parseNestedXmlTools(processed);
1178
1354
  const openingTagRegex = /<tool_call>|<tool>/gi;
1179
1355
  const closingTagRegex = /<\/tool_call>|<\/tool>/gi;
1180
1356
  const openingMatches = processed.match(openingTagRegex) || [];
@@ -1211,7 +1387,7 @@ function preprocessText(text) {
1211
1387
  }
1212
1388
  }
1213
1389
  }
1214
- return toolCallLines;
1390
+ return { lines: toolCallLines, nestedTools };
1215
1391
  }
1216
1392
  var LLMResponseParser = class {
1217
1393
  finishSpecSplitRe = /,(?=[^,\s]+:)/;
@@ -1219,8 +1395,8 @@ var LLMResponseParser = class {
1219
1395
  if (typeof text !== "string") {
1220
1396
  throw new TypeError("Command text must be a string.");
1221
1397
  }
1222
- const lines = preprocessText(text);
1223
- const commands = [];
1398
+ const { lines, nestedTools } = preprocessText(text);
1399
+ const commands = [...nestedTools];
1224
1400
  let finishAccumulator = null;
1225
1401
  lines.forEach((line) => {
1226
1402
  if (!line || line.startsWith("#")) return;
@@ -1228,8 +1404,8 @@ var LLMResponseParser = class {
1228
1404
  if (parts.length === 0) return;
1229
1405
  const cmd = parts[0];
1230
1406
  switch (cmd) {
1231
- case "analyse":
1232
- this.handleAnalyse(parts, line, commands);
1407
+ case "list_directory":
1408
+ this.handleListDirectory(parts, line, commands);
1233
1409
  break;
1234
1410
  case "grep":
1235
1411
  this.handleGrep(parts, line, commands);
@@ -1247,8 +1423,8 @@ var LLMResponseParser = class {
1247
1423
  if (finishAccumulator) {
1248
1424
  const map = finishAccumulator;
1249
1425
  const entries = [...map.entries()];
1250
- const filesPayload = entries.map(([path4, ranges]) => ({
1251
- path: path4,
1426
+ const filesPayload = entries.map(([path5, ranges]) => ({
1427
+ path: path5,
1252
1428
  lines: [...ranges].sort((a, b) => a[0] - b[0])
1253
1429
  }));
1254
1430
  commands.push({ name: "finish", arguments: { files: filesPayload } });
@@ -1280,18 +1456,17 @@ var LLMResponseParser = class {
1280
1456
  skip(message) {
1281
1457
  return { name: "_skip", arguments: { message } };
1282
1458
  }
1283
- handleAnalyse(parts, rawLine, commands) {
1459
+ handleListDirectory(parts, rawLine, commands) {
1284
1460
  if (parts.length < 2) {
1285
1461
  commands.push(this.skip(
1286
- `[SKIPPED] Your command "${rawLine}" is missing a path. Correct format: analyse <path> [pattern]. Example: analyse src/`
1462
+ `[SKIPPED] Your command "${rawLine}" is missing a path. Correct format: list_directory <path> [pattern]. Example: list_directory src/`
1287
1463
  ));
1288
1464
  return;
1289
1465
  }
1290
- const path4 = parts[1];
1466
+ const path5 = parts[1];
1291
1467
  const pattern = parts[2]?.replace(/^"|"$/g, "") ?? null;
1292
- commands.push({ name: "analyse", arguments: { path: path4, pattern } });
1468
+ commands.push({ name: "list_directory", arguments: { path: path5, pattern } });
1293
1469
  }
1294
- // no glob tool in MCP
1295
1470
  handleGrep(parts, rawLine, commands) {
1296
1471
  if (parts.length < 3) {
1297
1472
  commands.push(this.skip(
@@ -1362,8 +1537,30 @@ var LLMResponseParser = class {
1362
1537
  }
1363
1538
  };
1364
1539
 
1365
- // tools/warp_grep/tools/read.ts
1540
+ // tools/warp_grep/agent/tools/grep.ts
1541
+ async function toolGrep(provider, args) {
1542
+ const res = await provider.grep(args);
1543
+ if (res.error) {
1544
+ return { output: res.error };
1545
+ }
1546
+ if (!res.lines.length) {
1547
+ return { output: "no matches" };
1548
+ }
1549
+ return { output: res.lines.join("\n") };
1550
+ }
1551
+
1552
+ // tools/warp_grep/agent/tools/read.ts
1366
1553
  async function toolRead(provider, args) {
1554
+ if (args.lines && args.lines.length > 0) {
1555
+ const chunks = [];
1556
+ for (const [start, end] of args.lines) {
1557
+ const res2 = await provider.read({ path: args.path, start, end });
1558
+ if (res2.error) return res2.error;
1559
+ chunks.push(res2.lines.join("\n"));
1560
+ }
1561
+ if (chunks.every((c) => c === "")) return "(empty file)";
1562
+ return chunks.join("\n...\n");
1563
+ }
1367
1564
  const res = await provider.read({ path: args.path, start: args.start, end: args.end });
1368
1565
  if (res.error) {
1369
1566
  return res.error;
@@ -1372,16 +1569,56 @@ async function toolRead(provider, args) {
1372
1569
  return res.lines.join("\n");
1373
1570
  }
1374
1571
 
1375
- // tools/warp_grep/tools/analyse.ts
1376
- async function toolAnalyse(provider, args) {
1377
- const list = await provider.analyse({
1572
+ // tools/warp_grep/agent/tools/list_directory.ts
1573
+ async function toolListDirectory(provider, args) {
1574
+ const list = await provider.listDirectory({
1378
1575
  path: args.path,
1379
1576
  pattern: args.pattern ?? null,
1380
- maxResults: args.maxResults ?? 100,
1381
- maxDepth: args.maxDepth ?? 2
1577
+ maxResults: args.maxResults ?? AGENT_CONFIG.MAX_OUTPUT_LINES,
1578
+ maxDepth: args.maxDepth ?? AGENT_CONFIG.MAX_LIST_DEPTH
1382
1579
  });
1383
1580
  if (!list.length) return "empty";
1384
- return list.map((e) => `${" ".repeat(e.depth)}- ${e.type === "dir" ? "[D]" : "[F]"} ${e.name}`).join("\n");
1581
+ if (list.length >= AGENT_CONFIG.MAX_OUTPUT_LINES) {
1582
+ return "query not specific enough, tool called tried to return too much context and failed";
1583
+ }
1584
+ return list.map((e) => {
1585
+ const indent = " ".repeat(e.depth);
1586
+ const name = e.type === "dir" ? `${e.name}/` : e.name;
1587
+ return `${indent}${name}`;
1588
+ }).join("\n");
1589
+ }
1590
+
1591
+ // tools/warp_grep/agent/tools/finish.ts
1592
+ async function readFinishFiles(repoRoot, files, reader) {
1593
+ const out = [];
1594
+ for (const f of files) {
1595
+ const ranges = mergeRanges(f.lines);
1596
+ const chunks = [];
1597
+ for (const [s, e] of ranges) {
1598
+ const lines = await reader(f.path, s, e);
1599
+ chunks.push(lines.join("\n"));
1600
+ }
1601
+ out.push({ path: f.path, ranges, content: chunks.join("\n") });
1602
+ }
1603
+ return out;
1604
+ }
1605
+ function mergeRanges(ranges) {
1606
+ if (!ranges.length) return [];
1607
+ const sorted = [...ranges].sort((a, b) => a[0] - b[0]);
1608
+ const merged = [];
1609
+ let [cs, ce] = sorted[0];
1610
+ for (let i = 1; i < sorted.length; i++) {
1611
+ const [s, e] = sorted[i];
1612
+ if (s <= ce + 1) {
1613
+ ce = Math.max(ce, e);
1614
+ } else {
1615
+ merged.push([cs, ce]);
1616
+ cs = s;
1617
+ ce = e;
1618
+ }
1619
+ }
1620
+ merged.push([cs, ce]);
1621
+ return merged;
1385
1622
  }
1386
1623
 
1387
1624
  // tools/warp_grep/agent/formatter.ts
@@ -1400,8 +1637,8 @@ var ToolOutputFormatter = class {
1400
1637
  switch (name) {
1401
1638
  case "read":
1402
1639
  return this.formatRead(safeArgs, payload, isError);
1403
- case "analyse":
1404
- return this.formatAnalyse(safeArgs, payload, isError);
1640
+ case "list_directory":
1641
+ return this.formatListDirectory(safeArgs, payload, isError);
1405
1642
  case "grep":
1406
1643
  return this.formatGrep(safeArgs, payload, isError);
1407
1644
  default:
@@ -1414,39 +1651,56 @@ ${payload}
1414
1651
  if (isError) {
1415
1652
  return payload;
1416
1653
  }
1417
- const path4 = this.asString(args.path) || "...";
1418
- return `<file path="${path4}">
1654
+ const path5 = this.asString(args.path) || "...";
1655
+ const start = args.start;
1656
+ const end = args.end;
1657
+ const linesArray = args.lines;
1658
+ const attributes = [`path="${path5}"`];
1659
+ if (linesArray && linesArray.length > 0) {
1660
+ const rangeStr = linesArray.map(([s, e]) => `${s}-${e}`).join(",");
1661
+ attributes.push(`lines="${rangeStr}"`);
1662
+ } else if (start !== void 0 && end !== void 0) {
1663
+ attributes.push(`lines="${start}-${end}"`);
1664
+ }
1665
+ return `<read ${attributes.join(" ")}>
1419
1666
  ${payload}
1420
- </file>`;
1667
+ </read>`;
1421
1668
  }
1422
- formatAnalyse(args, payload, isError) {
1423
- const path4 = this.asString(args.path) || ".";
1669
+ formatListDirectory(args, payload, isError) {
1670
+ const path5 = this.asString(args.path) || ".";
1671
+ const pattern = this.asString(args.pattern);
1672
+ const attributes = [`path="${path5}"`];
1673
+ if (pattern) {
1674
+ attributes.push(`pattern="${pattern}"`);
1675
+ }
1424
1676
  if (isError) {
1425
- return `<analyse_results path="${path4}" status="error">
1426
- ${payload}
1427
- </analyse_results>`;
1677
+ attributes.push('status="error"');
1428
1678
  }
1429
- return `<analyse_results path="${path4}">
1679
+ return `<list_directory ${attributes.join(" ")}>
1430
1680
  ${payload}
1431
- </analyse_results>`;
1681
+ </list_directory>`;
1432
1682
  }
1433
1683
  formatGrep(args, payload, isError) {
1434
1684
  const pattern = this.asString(args.pattern);
1435
- const path4 = this.asString(args.path);
1685
+ const subDir = this.asString(args.path);
1686
+ const glob = this.asString(args.glob);
1436
1687
  const attributes = [];
1437
1688
  if (pattern !== void 0) {
1438
1689
  attributes.push(`pattern="${pattern}"`);
1439
1690
  }
1440
- if (path4 !== void 0) {
1441
- attributes.push(`path="${path4}"`);
1691
+ if (subDir !== void 0) {
1692
+ attributes.push(`sub_dir="${subDir}"`);
1693
+ }
1694
+ if (glob !== void 0) {
1695
+ attributes.push(`glob="${glob}"`);
1442
1696
  }
1443
1697
  if (isError) {
1444
1698
  attributes.push('status="error"');
1445
1699
  }
1446
1700
  const attrText = attributes.length ? ` ${attributes.join(" ")}` : "";
1447
- return `<grep_output${attrText}>
1701
+ return `<grep${attrText}>
1448
1702
  ${payload}
1449
- </grep_output>`;
1703
+ </grep>`;
1450
1704
  }
1451
1705
  asString(value) {
1452
1706
  if (value === null || value === void 0) {
@@ -1460,180 +1714,100 @@ function formatAgentToolOutput(toolName, args, output, options = {}) {
1460
1714
  return sharedFormatter.format(toolName, args, output, options);
1461
1715
  }
1462
1716
 
1463
- // tools/warp_grep/agent/grep_helpers.ts
1464
- var GrepState = class {
1465
- seenLines = /* @__PURE__ */ new Set();
1466
- isNew(path4, lineNumber) {
1467
- const key = this.makeKey(path4, lineNumber);
1468
- return !this.seenLines.has(key);
1469
- }
1470
- add(path4, lineNumber) {
1471
- this.seenLines.add(this.makeKey(path4, lineNumber));
1472
- }
1473
- makeKey(path4, lineNumber) {
1474
- return `${path4}:${lineNumber}`;
1475
- }
1476
- };
1477
- var MAX_GREP_OUTPUT_CHARS_PER_TURN = 6e4;
1478
- function extractMatchFields(payload) {
1479
- const text = payload.replace(/\r?\n$/, "");
1480
- if (!text || text.startsWith("[error]")) {
1481
- return null;
1482
- }
1483
- const firstSep = text.indexOf(":");
1484
- if (firstSep === -1) {
1485
- return null;
1486
- }
1487
- let filePath = text.slice(0, firstSep).trim();
1488
- if (!filePath) {
1489
- return null;
1490
- }
1491
- if (filePath.startsWith("./") || filePath.startsWith(".\\")) {
1492
- filePath = filePath.slice(2);
1493
- }
1494
- const remainder = text.slice(firstSep + 1);
1495
- const secondSep = remainder.indexOf(":");
1496
- if (secondSep === -1) {
1497
- return null;
1498
- }
1499
- const linePart = remainder.slice(0, secondSep);
1500
- const lineNumber = Number.parseInt(linePart, 10);
1501
- if (!Number.isInteger(lineNumber) || lineNumber <= 0) {
1502
- return null;
1503
- }
1504
- let contentSegment = remainder.slice(secondSep + 1);
1505
- const columnSep = contentSegment.indexOf(":");
1506
- if (columnSep !== -1 && /^\d+$/.test(contentSegment.slice(0, columnSep))) {
1507
- contentSegment = contentSegment.slice(columnSep + 1);
1508
- }
1509
- const content = contentSegment.trim();
1510
- if (!content) {
1511
- return null;
1512
- }
1513
- return { path: filePath, lineNumber, content };
1514
- }
1515
- function parseAndFilterGrepOutput(rawOutput, state) {
1516
- const matches = [];
1517
- if (typeof rawOutput !== "string" || !rawOutput.trim()) {
1518
- return matches;
1519
- }
1520
- for (const line of rawOutput.split(/\r?\n/)) {
1521
- const fields = extractMatchFields(line);
1522
- if (!fields) {
1523
- continue;
1524
- }
1525
- if (state.isNew(fields.path, fields.lineNumber)) {
1526
- matches.push(fields);
1527
- state.add(fields.path, fields.lineNumber);
1528
- }
1529
- }
1530
- return matches;
1531
- }
1532
- function truncateOutput(payload, maxChars) {
1533
- if (payload.length <= maxChars) {
1534
- return payload;
1535
- }
1536
- const note = "... (output truncated)";
1537
- const available = maxChars - note.length - 1;
1538
- if (available <= 0) {
1539
- return note;
1540
- }
1541
- if (payload.length <= available) {
1542
- return `${payload.slice(0, available).replace(/\n$/, "")}
1543
- ${note}`;
1544
- }
1545
- const core = payload.slice(0, Math.max(0, available - 1));
1546
- const trimmed = core.replace(/\n$/, "").replace(/\s+$/, "");
1547
- const snippet = trimmed ? `${trimmed}\u2026` : "\u2026";
1548
- return `${snippet}
1549
- ${note}`;
1717
+ // tools/warp_grep/agent/helpers.ts
1718
+ var import_path2 = __toESM(require("path"), 1);
1719
+ var TRUNCATED_MARKER = "[truncated for context limit]";
1720
+ function formatTurnMessage(turnsUsed, maxTurns) {
1721
+ const turnsRemaining = maxTurns - turnsUsed;
1722
+ if (turnsRemaining === 1) {
1723
+ return `
1724
+ You have used ${turnsUsed} turns, you only have 1 turn remaining. You have run out of turns to explore the code base and MUST call the finish tool now`;
1725
+ }
1726
+ return `
1727
+ You have used ${turnsUsed} turn${turnsUsed === 1 ? "" : "s"} and have ${turnsRemaining} remaining`;
1728
+ }
1729
+ function calculateContextBudget(messages) {
1730
+ const totalChars = messages.reduce((sum, m) => sum + m.content.length, 0);
1731
+ const maxChars = AGENT_CONFIG.MAX_CONTEXT_CHARS;
1732
+ const percent = Math.round(totalChars / maxChars * 100);
1733
+ const usedK = Math.round(totalChars / 1e3);
1734
+ const maxK = Math.round(maxChars / 1e3);
1735
+ return `<context_budget>${percent}% (${usedK}K/${maxK}K chars)</context_budget>`;
1550
1736
  }
1551
- function formatTurnGrepOutput(matches, maxChars = MAX_GREP_OUTPUT_CHARS_PER_TURN) {
1552
- if (!matches || matches.length === 0) {
1553
- return "No new matches found.";
1554
- }
1555
- const matchesByFile = /* @__PURE__ */ new Map();
1556
- for (const match of matches) {
1557
- if (!matchesByFile.has(match.path)) {
1558
- matchesByFile.set(match.path, []);
1737
+ async function buildInitialState(repoRoot, query, provider) {
1738
+ try {
1739
+ const entries = await provider.listDirectory({
1740
+ path: ".",
1741
+ maxResults: AGENT_CONFIG.MAX_OUTPUT_LINES,
1742
+ maxDepth: 2
1743
+ });
1744
+ const treeLines = entries.map((e) => {
1745
+ const indent = " ".repeat(e.depth);
1746
+ const name = e.type === "dir" ? `${e.name}/` : e.name;
1747
+ return `${indent}${name}`;
1748
+ });
1749
+ const repoName = import_path2.default.basename(repoRoot);
1750
+ const treeOutput = treeLines.length > 0 ? `${repoName}/
1751
+ ${treeLines.join("\n")}` : `${repoName}/`;
1752
+ return `<repo_structure>
1753
+ ${treeOutput}
1754
+ </repo_structure>
1755
+
1756
+ <search_string>
1757
+ ${query}
1758
+ </search_string>`;
1759
+ } catch {
1760
+ const repoName = import_path2.default.basename(repoRoot);
1761
+ return `<repo_structure>
1762
+ ${repoName}/
1763
+ </repo_structure>
1764
+
1765
+ <search_string>
1766
+ ${query}
1767
+ </search_string>`;
1768
+ }
1769
+ }
1770
+ function enforceContextLimit(messages, maxChars = AGENT_CONFIG.MAX_CONTEXT_CHARS) {
1771
+ const getTotalChars = () => messages.reduce((sum, m) => sum + m.content.length, 0);
1772
+ if (getTotalChars() <= maxChars) {
1773
+ return messages;
1774
+ }
1775
+ const userIndices = [];
1776
+ let firstUserSkipped = false;
1777
+ for (let i = 0; i < messages.length; i++) {
1778
+ if (messages[i].role === "user") {
1779
+ if (!firstUserSkipped) {
1780
+ firstUserSkipped = true;
1781
+ continue;
1782
+ }
1783
+ userIndices.push(i);
1559
1784
  }
1560
- matchesByFile.get(match.path).push(match);
1561
1785
  }
1562
- const lines = [];
1563
- const sortedPaths = Array.from(matchesByFile.keys()).sort();
1564
- sortedPaths.forEach((filePath, index) => {
1565
- if (index > 0) {
1566
- lines.push("");
1567
- }
1568
- lines.push(filePath);
1569
- const sortedMatches = matchesByFile.get(filePath).slice().sort((a, b) => a.lineNumber - b.lineNumber);
1570
- for (const match of sortedMatches) {
1571
- lines.push(`${match.lineNumber}:${match.content}`);
1786
+ for (const idx of userIndices) {
1787
+ if (getTotalChars() <= maxChars) {
1788
+ break;
1572
1789
  }
1573
- });
1574
- return truncateOutput(lines.join("\n"), maxChars);
1575
- }
1576
-
1577
- // tools/warp_grep/tools/finish.ts
1578
- async function readFinishFiles(repoRoot, files, reader) {
1579
- const out = [];
1580
- for (const f of files) {
1581
- const ranges = mergeRanges(f.lines);
1582
- const chunks = [];
1583
- for (const [s, e] of ranges) {
1584
- const lines = await reader(f.path, s, e);
1585
- chunks.push(lines.join("\n"));
1790
+ if (messages[idx].content !== TRUNCATED_MARKER) {
1791
+ messages[idx] = { role: "user", content: TRUNCATED_MARKER };
1586
1792
  }
1587
- out.push({ path: f.path, ranges, content: chunks.join("\n") });
1588
1793
  }
1589
- return out;
1590
- }
1591
- function mergeRanges(ranges) {
1592
- if (!ranges.length) return [];
1593
- const sorted = [...ranges].sort((a, b) => a[0] - b[0]);
1594
- const merged = [];
1595
- let [cs, ce] = sorted[0];
1596
- for (let i = 1; i < sorted.length; i++) {
1597
- const [s, e] = sorted[i];
1598
- if (s <= ce + 1) {
1599
- ce = Math.max(ce, e);
1600
- } else {
1601
- merged.push([cs, ce]);
1602
- cs = s;
1603
- ce = e;
1604
- }
1605
- }
1606
- merged.push([cs, ce]);
1607
- return merged;
1794
+ return messages;
1608
1795
  }
1609
1796
 
1610
1797
  // tools/warp_grep/agent/runner.ts
1611
- var import_path2 = __toESM(require("path"), 1);
1798
+ var import_path3 = __toESM(require("path"), 1);
1612
1799
  var parser = new LLMResponseParser();
1613
- async function buildInitialState(repoRoot, query, provider) {
1614
- try {
1615
- const entries = await provider.analyse({ path: ".", maxResults: 100 });
1616
- const dirs = entries.filter((e) => e.type === "dir").map((d) => d.name).slice(0, 50);
1617
- const files = entries.filter((e) => e.type === "file").map((f) => f.name).slice(0, 50);
1618
- const parts = [
1619
- `<repo_root>${repoRoot}</repo_root>`,
1620
- `<top_dirs>${dirs.join(", ")}</top_dirs>`,
1621
- `<top_files>${files.join(", ")}</top_files>`
1622
- ];
1623
- return parts.join("\n");
1624
- } catch {
1625
- return `<repo_root>${repoRoot}</repo_root>`;
1626
- }
1627
- }
1628
- async function callModel(messages, model, apiKey) {
1629
- const api = "https://api.morphllm.com/v1/chat/completions";
1800
+ var DEFAULT_API_URL = "https://api.morphllm.com";
1801
+ async function callModel(messages, model, options = {}) {
1802
+ const baseUrl = DEFAULT_API_URL;
1803
+ const apiKey = options.morphApiKey || process.env.MORPH_API_KEY || "";
1630
1804
  const fetchPromise = fetchWithRetry(
1631
- api,
1805
+ `${baseUrl}/v1/chat/completions`,
1632
1806
  {
1633
1807
  method: "POST",
1634
1808
  headers: {
1635
1809
  "Content-Type": "application/json",
1636
- Authorization: `Bearer ${apiKey || process.env.MORPH_API_KEY || ""}`
1810
+ Authorization: `Bearer ${apiKey}`
1637
1811
  },
1638
1812
  body: JSON.stringify({
1639
1813
  model,
@@ -1642,10 +1816,15 @@ async function callModel(messages, model, apiKey) {
1642
1816
  messages
1643
1817
  })
1644
1818
  },
1645
- {}
1819
+ options.retryConfig
1646
1820
  );
1647
1821
  const resp = await withTimeout(fetchPromise, AGENT_CONFIG.TIMEOUT_MS, "morph-warp-grep request timed out");
1648
1822
  if (!resp.ok) {
1823
+ if (resp.status === 404) {
1824
+ throw new Error(
1825
+ "The endpoint you are trying to call is likely deprecated. Please update with: npm cache clean --force && npx -y @morphllm/morphmcp@latest or visit: https://morphllm.com/mcp"
1826
+ );
1827
+ }
1649
1828
  const t = await resp.text();
1650
1829
  throw new Error(`morph-warp-grep error ${resp.status}: ${t}`);
1651
1830
  }
@@ -1657,23 +1836,24 @@ async function callModel(messages, model, apiKey) {
1657
1836
  return content;
1658
1837
  }
1659
1838
  async function runWarpGrep(config) {
1660
- const repoRoot = import_path2.default.resolve(config.repoRoot || process.cwd());
1839
+ const repoRoot = import_path3.default.resolve(config.repoRoot || process.cwd());
1661
1840
  const messages = [];
1662
- const systemMessage = { role: "system", content: getSystemPrompt() };
1663
- messages.push(systemMessage);
1664
- const queryContent = `<query>${config.query}</query>`;
1665
- messages.push({ role: "user", content: queryContent });
1841
+ messages.push({ role: "system", content: getSystemPrompt() });
1666
1842
  const initialState = await buildInitialState(repoRoot, config.query, config.provider);
1667
1843
  messages.push({ role: "user", content: initialState });
1668
- const maxRounds = AGENT_CONFIG.MAX_ROUNDS;
1844
+ const maxTurns = AGENT_CONFIG.MAX_TURNS;
1669
1845
  const model = config.model || DEFAULT_MODEL;
1670
1846
  const provider = config.provider;
1671
1847
  const errors = [];
1672
- const grepState = new GrepState();
1673
1848
  let finishMeta;
1674
1849
  let terminationReason = "terminated";
1675
- for (let round = 1; round <= maxRounds; round += 1) {
1676
- const assistantContent = await callModel(messages, model, config.apiKey).catch((e) => {
1850
+ for (let turn = 1; turn <= maxTurns; turn += 1) {
1851
+ enforceContextLimit(messages);
1852
+ const assistantContent = await callModel(messages, model, {
1853
+ morphApiKey: config.morphApiKey,
1854
+ morphApiUrl: config.morphApiUrl,
1855
+ retryConfig: config.retryConfig
1856
+ }).catch((e) => {
1677
1857
  errors.push({ message: e instanceof Error ? e.message : String(e) });
1678
1858
  return "";
1679
1859
  });
@@ -1681,13 +1861,13 @@ async function runWarpGrep(config) {
1681
1861
  messages.push({ role: "assistant", content: assistantContent });
1682
1862
  const toolCalls = parser.parse(assistantContent);
1683
1863
  if (toolCalls.length === 0) {
1684
- errors.push({ message: "No tool calls produced by the model." });
1864
+ errors.push({ message: "No tool calls produced by the model. Your MCP is likely out of date! Update it by running: npm cache clean --force && npx -y @morphllm/morphmcp@latest" });
1685
1865
  terminationReason = "terminated";
1686
1866
  break;
1687
1867
  }
1688
1868
  const finishCalls = toolCalls.filter((c) => c.name === "finish");
1689
1869
  const grepCalls = toolCalls.filter((c) => c.name === "grep");
1690
- const analyseCalls = toolCalls.filter((c) => c.name === "analyse");
1870
+ const listDirCalls = toolCalls.filter((c) => c.name === "list_directory");
1691
1871
  const readCalls = toolCalls.filter((c) => c.name === "read");
1692
1872
  const skipCalls = toolCalls.filter((c) => c.name === "_skip");
1693
1873
  const formatted = [];
@@ -1695,69 +1875,42 @@ async function runWarpGrep(config) {
1695
1875
  const msg = c.arguments?.message || "Command skipped due to parsing error";
1696
1876
  formatted.push(msg);
1697
1877
  }
1698
- const otherPromises = [];
1699
- for (const c of analyseCalls) {
1878
+ const allPromises = [];
1879
+ for (const c of grepCalls) {
1700
1880
  const args = c.arguments ?? {};
1701
- otherPromises.push(
1702
- toolAnalyse(provider, args).then(
1703
- (p) => formatAgentToolOutput("analyse", args, p, { isError: false }),
1704
- (err) => formatAgentToolOutput("analyse", args, String(err), { isError: true })
1881
+ allPromises.push(
1882
+ toolGrep(provider, args).then(
1883
+ ({ output }) => formatAgentToolOutput("grep", args, output, { isError: false }),
1884
+ (err) => formatAgentToolOutput("grep", args, String(err), { isError: true })
1885
+ )
1886
+ );
1887
+ }
1888
+ for (const c of listDirCalls) {
1889
+ const args = c.arguments ?? {};
1890
+ allPromises.push(
1891
+ toolListDirectory(provider, args).then(
1892
+ (p) => formatAgentToolOutput("list_directory", args, p, { isError: false }),
1893
+ (err) => formatAgentToolOutput("list_directory", args, String(err), { isError: true })
1705
1894
  )
1706
1895
  );
1707
1896
  }
1708
1897
  for (const c of readCalls) {
1709
1898
  const args = c.arguments ?? {};
1710
- otherPromises.push(
1899
+ allPromises.push(
1711
1900
  toolRead(provider, args).then(
1712
1901
  (p) => formatAgentToolOutput("read", args, p, { isError: false }),
1713
1902
  (err) => formatAgentToolOutput("read", args, String(err), { isError: true })
1714
1903
  )
1715
1904
  );
1716
1905
  }
1717
- const otherResults = await Promise.all(otherPromises);
1718
- formatted.push(...otherResults);
1719
- for (const c of grepCalls) {
1720
- const args = c.arguments ?? {};
1721
- try {
1722
- const grepRes = await provider.grep({ pattern: args.pattern, path: args.path });
1723
- if (grepRes.error) {
1724
- errors.push({ message: grepRes.error });
1725
- terminationReason = "terminated";
1726
- return {
1727
- terminationReason: "terminated",
1728
- messages,
1729
- errors
1730
- };
1731
- }
1732
- const rawOutput = Array.isArray(grepRes.lines) ? grepRes.lines.join("\n") : "";
1733
- const newMatches = parseAndFilterGrepOutput(rawOutput, grepState);
1734
- let formattedPayload = formatTurnGrepOutput(newMatches);
1735
- if (formattedPayload === "No new matches found.") {
1736
- formattedPayload = "no new matches";
1737
- }
1738
- formatted.push(formatAgentToolOutput("grep", args, formattedPayload, { isError: false }));
1739
- } catch (err) {
1740
- formatted.push(formatAgentToolOutput("grep", args, String(err), { isError: true }));
1741
- }
1906
+ const allResults = await Promise.all(allPromises);
1907
+ for (const result of allResults) {
1908
+ formatted.push(result);
1742
1909
  }
1743
1910
  if (formatted.length > 0) {
1744
- const turnsUsed = round;
1745
- const turnsRemaining = 4 - turnsUsed;
1746
- let turnMessage;
1747
- if (turnsRemaining === 0) {
1748
- turnMessage = `
1749
-
1750
- [Turn ${turnsUsed}/4] This is your LAST turn. You MUST call the finish tool now.`;
1751
- } else if (turnsRemaining === 1) {
1752
- turnMessage = `
1753
-
1754
- [Turn ${turnsUsed}/4] You have 1 turn remaining. Next turn you MUST call the finish tool.`;
1755
- } else {
1756
- turnMessage = `
1757
-
1758
- [Turn ${turnsUsed}/4] You have ${turnsRemaining} turns remaining.`;
1759
- }
1760
- messages.push({ role: "user", content: formatted.join("\n") + turnMessage });
1911
+ const turnMessage = formatTurnMessage(turn, maxTurns);
1912
+ const contextBudget = calculateContextBudget(messages);
1913
+ messages.push({ role: "user", content: formatted.join("\n") + turnMessage + "\n" + contextBudget });
1761
1914
  }
1762
1915
  if (finishCalls.length) {
1763
1916
  const fc = finishCalls[0];
@@ -1807,7 +1960,7 @@ async function runWarpGrep(config) {
1807
1960
 
1808
1961
  // tools/warp_grep/providers/local.ts
1809
1962
  var import_promises3 = __toESM(require("fs/promises"), 1);
1810
- var import_path4 = __toESM(require("path"), 1);
1963
+ var import_path5 = __toESM(require("path"), 1);
1811
1964
 
1812
1965
  // tools/warp_grep/utils/ripgrep.ts
1813
1966
  var import_child_process = require("child_process");
@@ -1872,21 +2025,21 @@ async function runRipgrep(args, opts) {
1872
2025
 
1873
2026
  // tools/warp_grep/utils/paths.ts
1874
2027
  var import_fs = __toESM(require("fs"), 1);
1875
- var import_path3 = __toESM(require("path"), 1);
2028
+ var import_path4 = __toESM(require("path"), 1);
1876
2029
  function resolveUnderRepo(repoRoot, targetPath) {
1877
- const absRoot = import_path3.default.resolve(repoRoot);
1878
- const resolved = import_path3.default.resolve(absRoot, targetPath);
2030
+ const absRoot = import_path4.default.resolve(repoRoot);
2031
+ const resolved = import_path4.default.resolve(absRoot, targetPath);
1879
2032
  ensureWithinRepo(absRoot, resolved);
1880
2033
  return resolved;
1881
2034
  }
1882
2035
  function ensureWithinRepo(repoRoot, absTarget) {
1883
- const rel = import_path3.default.relative(import_path3.default.resolve(repoRoot), import_path3.default.resolve(absTarget));
1884
- if (rel.startsWith("..") || import_path3.default.isAbsolute(rel)) {
2036
+ const rel = import_path4.default.relative(import_path4.default.resolve(repoRoot), import_path4.default.resolve(absTarget));
2037
+ if (rel.startsWith("..") || import_path4.default.isAbsolute(rel)) {
1885
2038
  throw new Error(`Path outside repository root: ${absTarget}`);
1886
2039
  }
1887
2040
  }
1888
2041
  function toRepoRelative(repoRoot, absPath) {
1889
- return import_path3.default.relative(import_path3.default.resolve(repoRoot), import_path3.default.resolve(absPath));
2042
+ return import_path4.default.relative(import_path4.default.resolve(repoRoot), import_path4.default.resolve(absPath));
1890
2043
  }
1891
2044
  function isSymlink(p) {
1892
2045
  try {
@@ -1929,10 +2082,18 @@ var LocalRipgrepProvider = class {
1929
2082
  this.excludes = excludes;
1930
2083
  }
1931
2084
  async grep(params) {
1932
- const abs = resolveUnderRepo(this.repoRoot, params.path);
2085
+ let abs;
2086
+ try {
2087
+ abs = resolveUnderRepo(this.repoRoot, params.path);
2088
+ } catch (err) {
2089
+ return {
2090
+ lines: [],
2091
+ error: `[PATH ERROR] ${err instanceof Error ? err.message : String(err)}`
2092
+ };
2093
+ }
1933
2094
  const stat = await import_promises3.default.stat(abs).catch(() => null);
1934
2095
  if (!stat) return { lines: [] };
1935
- const targetArg = abs === import_path4.default.resolve(this.repoRoot) ? "." : toRepoRelative(this.repoRoot, abs);
2096
+ const targetArg = abs === import_path5.default.resolve(this.repoRoot) ? "." : toRepoRelative(this.repoRoot, abs);
1936
2097
  const args = [
1937
2098
  "--no-config",
1938
2099
  "--no-heading",
@@ -1941,6 +2102,9 @@ var LocalRipgrepProvider = class {
1941
2102
  "--color=never",
1942
2103
  "--trim",
1943
2104
  "--max-columns=400",
2105
+ "-C",
2106
+ "1",
2107
+ ...params.glob ? ["--glob", params.glob] : [],
1944
2108
  ...this.excludes.flatMap((e) => ["-g", `!${e}`]),
1945
2109
  params.pattern,
1946
2110
  targetArg || "."
@@ -1965,29 +2129,24 @@ Details: ${res.stderr}` : ""}`
1965
2129
  };
1966
2130
  }
1967
2131
  const lines = (res.stdout || "").trim().split(/\r?\n/).filter((l) => l.length > 0);
1968
- return { lines };
1969
- }
1970
- async glob(params) {
1971
- const abs = resolveUnderRepo(this.repoRoot, params.path);
1972
- const targetArg = abs === import_path4.default.resolve(this.repoRoot) ? "." : toRepoRelative(this.repoRoot, abs);
1973
- const args = [
1974
- "--no-config",
1975
- "--files",
1976
- "-g",
1977
- params.pattern,
1978
- ...this.excludes.flatMap((e) => ["-g", `!${e}`]),
1979
- targetArg || "."
1980
- ];
1981
- const res = await runRipgrep(args, { cwd: this.repoRoot });
1982
- if (res.exitCode === -1) {
1983
- console.warn(`[warp_grep] ripgrep not available for glob: ${res.stderr || "execution failed"}`);
1984
- return { files: [] };
2132
+ if (lines.length > AGENT_CONFIG.MAX_OUTPUT_LINES) {
2133
+ return {
2134
+ lines: [],
2135
+ error: "query not specific enough, tool tried to return too much context and failed"
2136
+ };
1985
2137
  }
1986
- const files = (res.stdout || "").trim().split(/\r?\n/).filter((l) => l.length > 0);
1987
- return { files };
2138
+ return { lines };
1988
2139
  }
1989
2140
  async read(params) {
1990
- const abs = resolveUnderRepo(this.repoRoot, params.path);
2141
+ let abs;
2142
+ try {
2143
+ abs = resolveUnderRepo(this.repoRoot, params.path);
2144
+ } catch (err) {
2145
+ return {
2146
+ lines: [],
2147
+ error: `[PATH ERROR] ${err instanceof Error ? err.message : String(err)}`
2148
+ };
2149
+ }
1991
2150
  const stat = await import_promises3.default.stat(abs).catch(() => null);
1992
2151
  if (!stat || !stat.isFile()) {
1993
2152
  return {
@@ -2007,7 +2166,15 @@ Details: ${res.stderr}` : ""}`
2007
2166
  error: `[UNREADABLE FILE] You tried to read "${params.path}" but this file is either too large or not a text file, so it cannot be read. Try a different file.`
2008
2167
  };
2009
2168
  }
2010
- const lines = await readAllLines(abs);
2169
+ let lines;
2170
+ try {
2171
+ lines = await readAllLines(abs);
2172
+ } catch (err) {
2173
+ return {
2174
+ lines: [],
2175
+ error: `[READ ERROR] Failed to read "${params.path}": ${err instanceof Error ? err.message : String(err)}`
2176
+ };
2177
+ }
2011
2178
  const total = lines.length;
2012
2179
  let s = params.start ?? 1;
2013
2180
  let e = Math.min(params.end ?? total, total);
@@ -2020,30 +2187,46 @@ Details: ${res.stderr}` : ""}`
2020
2187
  const content = lines[i - 1] ?? "";
2021
2188
  out.push(`${i}|${content}`);
2022
2189
  }
2190
+ if (out.length > AGENT_CONFIG.MAX_READ_LINES) {
2191
+ const truncated = out.slice(0, AGENT_CONFIG.MAX_READ_LINES);
2192
+ truncated.push(`... [truncated: showing ${AGENT_CONFIG.MAX_READ_LINES} of ${out.length} lines]`);
2193
+ return { lines: truncated };
2194
+ }
2023
2195
  return { lines: out };
2024
2196
  }
2025
- async analyse(params) {
2026
- const abs = resolveUnderRepo(this.repoRoot, params.path);
2197
+ async listDirectory(params) {
2198
+ let abs;
2199
+ try {
2200
+ abs = resolveUnderRepo(this.repoRoot, params.path);
2201
+ } catch {
2202
+ return [];
2203
+ }
2027
2204
  const stat = await import_promises3.default.stat(abs).catch(() => null);
2028
2205
  if (!stat || !stat.isDirectory()) {
2029
2206
  return [];
2030
2207
  }
2031
- const maxResults = params.maxResults ?? 100;
2032
- const maxDepth = params.maxDepth ?? 2;
2208
+ const maxResults = params.maxResults ?? AGENT_CONFIG.MAX_OUTPUT_LINES;
2209
+ const maxDepth = params.maxDepth ?? AGENT_CONFIG.MAX_LIST_DEPTH;
2033
2210
  const regex = params.pattern ? new RegExp(params.pattern) : null;
2034
2211
  const results = [];
2212
+ let timedOut = false;
2213
+ const startTime = Date.now();
2035
2214
  async function walk(dir, depth) {
2215
+ if (Date.now() - startTime > AGENT_CONFIG.LIST_TIMEOUT_MS) {
2216
+ timedOut = true;
2217
+ return;
2218
+ }
2036
2219
  if (depth > maxDepth || results.length >= maxResults) return;
2037
2220
  const entries = await import_promises3.default.readdir(dir, { withFileTypes: true });
2038
2221
  for (const entry of entries) {
2039
- const full = import_path4.default.join(dir, entry.name);
2222
+ if (timedOut || results.length >= maxResults) break;
2223
+ const full = import_path5.default.join(dir, entry.name);
2040
2224
  const rel = toRepoRelative(abs, full).replace(/^[.][/\\]?/, "");
2041
- if (DEFAULT_EXCLUDES.some((ex) => rel.split(import_path4.default.sep).includes(ex))) continue;
2225
+ if (DEFAULT_EXCLUDES.some((ex) => rel.split(import_path5.default.sep).includes(ex))) continue;
2042
2226
  if (regex && !regex.test(entry.name)) continue;
2043
- if (results.length >= maxResults) break;
2044
2227
  results.push({
2045
2228
  name: entry.name,
2046
- path: toRepoRelative(import_path4.default.resolve(""), full),
2229
+ path: toRepoRelative(import_path5.default.resolve(""), full),
2047
2230
  // relative display
2048
2231
  type: entry.isDirectory() ? "dir" : "file",
2049
2232
  depth
@@ -2058,12 +2241,103 @@ Details: ${res.stderr}` : ""}`
2058
2241
  }
2059
2242
  };
2060
2243
 
2061
- // tools/warp_grep/core.ts
2244
+ // tools/warp_grep/providers/remote.ts
2245
+ var RemoteCommandsProvider = class {
2246
+ constructor(repoRoot, commands) {
2247
+ this.repoRoot = repoRoot;
2248
+ this.commands = commands;
2249
+ }
2250
+ /**
2251
+ * Run grep command and parse ripgrep output
2252
+ */
2253
+ async grep(params) {
2254
+ try {
2255
+ const stdout = await this.commands.grep(params.pattern, params.path, params.glob);
2256
+ const lines = (stdout || "").trim().split(/\r?\n/).filter((l) => l.length > 0);
2257
+ if (lines.length > AGENT_CONFIG.MAX_OUTPUT_LINES) {
2258
+ return {
2259
+ lines: [],
2260
+ error: "Query not specific enough - too many results returned. Try a more specific pattern."
2261
+ };
2262
+ }
2263
+ return { lines };
2264
+ } catch (error) {
2265
+ return {
2266
+ lines: [],
2267
+ error: `[GREP ERROR] ${error instanceof Error ? error.message : String(error)}`
2268
+ };
2269
+ }
2270
+ }
2271
+ /**
2272
+ * Read file and add line numbers
2273
+ */
2274
+ async read(params) {
2275
+ const start = params.start ?? 1;
2276
+ const end = params.end ?? 1e6;
2277
+ try {
2278
+ const stdout = await this.commands.read(params.path, start, end);
2279
+ const contentLines = (stdout || "").split("\n");
2280
+ if (contentLines.length > 0 && contentLines[contentLines.length - 1] === "") {
2281
+ contentLines.pop();
2282
+ }
2283
+ const lines = contentLines.map((content, idx) => `${start + idx}|${content}`);
2284
+ if (lines.length > AGENT_CONFIG.MAX_READ_LINES) {
2285
+ const truncated = lines.slice(0, AGENT_CONFIG.MAX_READ_LINES);
2286
+ truncated.push(`... [truncated: showing ${AGENT_CONFIG.MAX_READ_LINES} of ${lines.length} lines]`);
2287
+ return { lines: truncated };
2288
+ }
2289
+ return { lines };
2290
+ } catch (error) {
2291
+ return {
2292
+ lines: [],
2293
+ error: `[READ ERROR] ${error instanceof Error ? error.message : String(error)}`
2294
+ };
2295
+ }
2296
+ }
2297
+ /**
2298
+ * List directory and parse find output
2299
+ */
2300
+ async listDirectory(params) {
2301
+ const maxDepth = params.maxDepth ?? AGENT_CONFIG.MAX_LIST_DEPTH;
2302
+ const maxResults = params.maxResults ?? AGENT_CONFIG.MAX_OUTPUT_LINES;
2303
+ try {
2304
+ const stdout = await this.commands.listDir(params.path, maxDepth);
2305
+ const paths = (stdout || "").trim().split(/\r?\n/).filter((p) => p.length > 0);
2306
+ const regex = params.pattern ? new RegExp(params.pattern) : null;
2307
+ const entries = [];
2308
+ for (const fullPath of paths) {
2309
+ if (fullPath === params.path || fullPath === this.repoRoot) continue;
2310
+ const name = fullPath.split("/").pop() || "";
2311
+ if (regex && !regex.test(name)) continue;
2312
+ let relativePath = fullPath;
2313
+ if (fullPath.startsWith(this.repoRoot)) {
2314
+ relativePath = fullPath.slice(this.repoRoot.length).replace(/^\//, "");
2315
+ }
2316
+ const depth = relativePath.split("/").filter(Boolean).length - 1;
2317
+ const hasExtension = name.includes(".") && !name.startsWith(".");
2318
+ const type = hasExtension ? "file" : "dir";
2319
+ entries.push({
2320
+ name,
2321
+ path: relativePath,
2322
+ type,
2323
+ depth: Math.max(0, depth)
2324
+ });
2325
+ if (entries.length >= maxResults) break;
2326
+ }
2327
+ return entries;
2328
+ } catch (error) {
2329
+ return [];
2330
+ }
2331
+ }
2332
+ };
2333
+
2334
+ // tools/warp_grep/client.ts
2062
2335
  var WarpGrepClient = class {
2063
2336
  config;
2064
2337
  constructor(config = {}) {
2065
2338
  this.config = {
2066
- apiKey: config.apiKey,
2339
+ morphApiKey: config.morphApiKey,
2340
+ morphApiUrl: config.morphApiUrl,
2067
2341
  debug: config.debug,
2068
2342
  timeout: config.timeout,
2069
2343
  retryConfig: config.retryConfig
@@ -2091,34 +2365,46 @@ var WarpGrepClient = class {
2091
2365
  * ```
2092
2366
  */
2093
2367
  async execute(input) {
2094
- const provider = input.provider ?? new LocalRipgrepProvider(input.repoRoot, input.excludes);
2095
- const result = await runWarpGrep({
2096
- query: input.query,
2097
- repoRoot: input.repoRoot,
2098
- provider,
2099
- excludes: input.excludes,
2100
- includes: input.includes,
2101
- debug: input.debug ?? this.config.debug ?? false,
2102
- apiKey: this.config.apiKey
2103
- });
2104
- const finish = result.finish;
2105
- if (result.terminationReason !== "completed" || !finish?.metadata) {
2106
- return {
2107
- success: false,
2108
- error: "Search did not complete"
2109
- };
2110
- }
2111
- const contexts = (finish.resolved ?? []).map((r) => ({
2112
- file: r.path,
2113
- content: r.content
2114
- }));
2115
- return {
2116
- success: true,
2117
- contexts,
2118
- summary: finish.payload
2119
- };
2368
+ return executeToolCall(
2369
+ { query: input.query },
2370
+ {
2371
+ repoRoot: input.repoRoot,
2372
+ remoteCommands: input.remoteCommands,
2373
+ provider: input.provider,
2374
+ excludes: input.excludes,
2375
+ includes: input.includes,
2376
+ debug: input.debug ?? this.config.debug,
2377
+ morphApiKey: this.config.morphApiKey,
2378
+ morphApiUrl: this.config.morphApiUrl,
2379
+ retryConfig: this.config.retryConfig
2380
+ }
2381
+ );
2120
2382
  }
2121
2383
  };
2384
+ async function executeToolCall(input, config) {
2385
+ const parsed = typeof input === "string" ? JSON.parse(input) : input;
2386
+ const provider = config.remoteCommands ? new RemoteCommandsProvider(config.repoRoot, config.remoteCommands) : config.provider ?? new LocalRipgrepProvider(config.repoRoot, config.excludes);
2387
+ const result = await runWarpGrep({
2388
+ query: parsed.query,
2389
+ repoRoot: config.repoRoot,
2390
+ provider,
2391
+ excludes: config.excludes,
2392
+ includes: config.includes,
2393
+ debug: config.debug ?? false,
2394
+ morphApiKey: config.morphApiKey,
2395
+ morphApiUrl: config.morphApiUrl,
2396
+ retryConfig: config.retryConfig
2397
+ });
2398
+ const finish = result.finish;
2399
+ if (result.terminationReason !== "completed" || !finish?.metadata) {
2400
+ return { success: false, error: "Search did not complete" };
2401
+ }
2402
+ const contexts = (finish.resolved ?? []).map((r) => ({
2403
+ file: r.path,
2404
+ content: r.content
2405
+ }));
2406
+ return { success: true, contexts, summary: finish.payload };
2407
+ }
2122
2408
  function formatResult(result) {
2123
2409
  if (!result.success) {
2124
2410
  return `Search failed: ${result.error}`;
@@ -2898,29 +3184,7 @@ var TOOL_PARAMETERS = {
2898
3184
  },
2899
3185
  required: ["query"]
2900
3186
  };
2901
- async function execute(input, config) {
2902
- const parsed = typeof input === "string" ? JSON.parse(input) : input;
2903
- const provider = config.provider ?? new LocalRipgrepProvider(config.repoRoot, config.excludes);
2904
- const result = await runWarpGrep({
2905
- query: parsed.query,
2906
- repoRoot: config.repoRoot,
2907
- provider,
2908
- excludes: config.excludes,
2909
- includes: config.includes,
2910
- debug: config.debug ?? false,
2911
- apiKey: config.apiKey
2912
- });
2913
- const finish = result.finish;
2914
- if (result.terminationReason !== "completed" || !finish?.metadata) {
2915
- return { success: false, error: "Search did not complete" };
2916
- }
2917
- const contexts = (finish.resolved ?? []).map((r) => ({
2918
- file: r.path,
2919
- content: r.content
2920
- }));
2921
- return { success: true, contexts, summary: finish.payload };
2922
- }
2923
- function createMorphWarpGrepTool(config) {
3187
+ function createWarpGrepTool(config) {
2924
3188
  const tool4 = {
2925
3189
  type: "function",
2926
3190
  function: {
@@ -2931,7 +3195,7 @@ function createMorphWarpGrepTool(config) {
2931
3195
  };
2932
3196
  return Object.assign(tool4, {
2933
3197
  execute: async (input) => {
2934
- return execute(input, config);
3198
+ return executeToolCall(input, config);
2935
3199
  },
2936
3200
  formatResult: (result) => {
2937
3201
  return formatResult(result);
@@ -3107,7 +3371,7 @@ var editFileTool = {
3107
3371
  }
3108
3372
  }
3109
3373
  };
3110
- async function execute2(input, config) {
3374
+ async function execute(input, config) {
3111
3375
  return executeEditFile(input, config);
3112
3376
  }
3113
3377
  function getSystemPrompt2() {
@@ -3145,7 +3409,7 @@ function createEditFileTool(config = {}) {
3145
3409
  return Object.assign({}, toolDef, {
3146
3410
  execute: async (input) => {
3147
3411
  const parsedInput = typeof input === "string" ? JSON.parse(input) : input;
3148
- return execute2(parsedInput, config);
3412
+ return execute(parsedInput, config);
3149
3413
  },
3150
3414
  formatResult: (result) => {
3151
3415
  return formatResult3(result);
@@ -3164,13 +3428,13 @@ var OpenAIToolFactory = class {
3164
3428
  /**
3165
3429
  * Create an OpenAI-compatible warp grep tool
3166
3430
  *
3167
- * @param toolConfig - Tool configuration (apiKey inherited from MorphClient)
3431
+ * @param toolConfig - Tool configuration (morphApiKey inherited from MorphClient)
3168
3432
  * @returns OpenAI ChatCompletionTool with execute and formatResult methods
3169
3433
  */
3170
3434
  createWarpGrepTool(toolConfig) {
3171
- return createMorphWarpGrepTool({
3435
+ return createWarpGrepTool({
3172
3436
  ...toolConfig,
3173
- apiKey: this.config.apiKey
3437
+ morphApiKey: this.config.apiKey
3174
3438
  });
3175
3439
  }
3176
3440
  /**
@@ -3207,29 +3471,7 @@ var INPUT_SCHEMA = {
3207
3471
  },
3208
3472
  required: ["query"]
3209
3473
  };
3210
- async function execute3(input, config) {
3211
- const parsed = typeof input === "string" ? JSON.parse(input) : input;
3212
- const provider = config.provider ?? new LocalRipgrepProvider(config.repoRoot, config.excludes);
3213
- const result = await runWarpGrep({
3214
- query: parsed.query,
3215
- repoRoot: config.repoRoot,
3216
- provider,
3217
- excludes: config.excludes,
3218
- includes: config.includes,
3219
- debug: config.debug ?? false,
3220
- apiKey: config.apiKey
3221
- });
3222
- const finish = result.finish;
3223
- if (result.terminationReason !== "completed" || !finish?.metadata) {
3224
- return { success: false, error: "Search did not complete" };
3225
- }
3226
- const contexts = (finish.resolved ?? []).map((r) => ({
3227
- file: r.path,
3228
- content: r.content
3229
- }));
3230
- return { success: true, contexts, summary: finish.payload };
3231
- }
3232
- function createMorphWarpGrepTool2(config) {
3474
+ function createWarpGrepTool2(config) {
3233
3475
  const tool4 = {
3234
3476
  name: config.name ?? WARP_GREP_TOOL_NAME,
3235
3477
  description: config.description ?? WARP_GREP_DESCRIPTION,
@@ -3237,7 +3479,7 @@ function createMorphWarpGrepTool2(config) {
3237
3479
  };
3238
3480
  return Object.assign(tool4, {
3239
3481
  execute: async (input) => {
3240
- return execute3(input, config);
3482
+ return executeToolCall(input, config);
3241
3483
  },
3242
3484
  formatResult: (result) => {
3243
3485
  return formatResult(result);
@@ -3387,13 +3629,13 @@ var AnthropicToolFactory = class {
3387
3629
  /**
3388
3630
  * Create an Anthropic-compatible warp grep tool
3389
3631
  *
3390
- * @param toolConfig - Tool configuration (apiKey inherited from MorphClient)
3632
+ * @param toolConfig - Tool configuration (morphApiKey inherited from MorphClient)
3391
3633
  * @returns Anthropic Tool with execute and formatResult methods
3392
3634
  */
3393
3635
  createWarpGrepTool(toolConfig) {
3394
- return createMorphWarpGrepTool2({
3636
+ return createWarpGrepTool2({
3395
3637
  ...toolConfig,
3396
- apiKey: this.config.apiKey
3638
+ morphApiKey: this.config.apiKey
3397
3639
  });
3398
3640
  }
3399
3641
  /**
@@ -3425,33 +3667,23 @@ var AnthropicToolFactory = class {
3425
3667
  // tools/warp_grep/vercel.ts
3426
3668
  var import_ai = require("ai");
3427
3669
  var import_zod = require("zod");
3428
- var warpGrepSchema = import_zod.z.object({
3429
- query: import_zod.z.string().describe("Free-form repository question")
3430
- });
3431
- function createMorphWarpGrepTool3(config) {
3670
+ function createWarpGrepTool3(config) {
3671
+ const schema = import_zod.z.object({
3672
+ query: import_zod.z.string().describe("Free-form repository question")
3673
+ });
3432
3674
  return (0, import_ai.tool)({
3433
3675
  description: config.description ?? WARP_GREP_DESCRIPTION,
3434
- inputSchema: warpGrepSchema,
3676
+ inputSchema: schema,
3435
3677
  execute: async (params) => {
3436
- const provider = config.provider ?? new LocalRipgrepProvider(config.repoRoot, config.excludes);
3437
- const result = await runWarpGrep({
3438
- query: params.query,
3439
- repoRoot: config.repoRoot,
3440
- provider,
3441
- excludes: config.excludes,
3442
- includes: config.includes,
3443
- debug: config.debug ?? false,
3444
- apiKey: config.apiKey
3445
- });
3446
- const finish = result.finish;
3447
- if (result.terminationReason !== "completed" || !finish?.metadata) {
3448
- return { success: false, error: "Search did not complete" };
3678
+ const result = await executeToolCall(params, config);
3679
+ if (!result.success) {
3680
+ throw new Error(`Failed to search codebase: ${result.error}`);
3449
3681
  }
3450
- const contexts = (finish.resolved ?? []).map((r) => ({
3451
- file: r.path,
3452
- content: r.content
3453
- }));
3454
- return { success: true, contexts, summary: finish.payload };
3682
+ return {
3683
+ success: true,
3684
+ contexts: result.contexts,
3685
+ summary: result.summary
3686
+ };
3455
3687
  }
3456
3688
  });
3457
3689
  }
@@ -3564,13 +3796,13 @@ var VercelToolFactory = class {
3564
3796
  /**
3565
3797
  * Create a Vercel AI SDK-compatible warp grep tool
3566
3798
  *
3567
- * @param toolConfig - Tool configuration (apiKey inherited from MorphClient)
3799
+ * @param toolConfig - Tool configuration (morphApiKey inherited from MorphClient)
3568
3800
  * @returns Vercel AI SDK tool
3569
3801
  */
3570
3802
  createWarpGrepTool(toolConfig) {
3571
- return createMorphWarpGrepTool3({
3803
+ return createWarpGrepTool3({
3572
3804
  ...toolConfig,
3573
- apiKey: this.config.apiKey
3805
+ morphApiKey: this.config.apiKey
3574
3806
  });
3575
3807
  }
3576
3808
  /**
@@ -3650,7 +3882,7 @@ var MorphClient = class {
3650
3882
  retryConfig: config.retryConfig
3651
3883
  });
3652
3884
  this.warpGrep = new WarpGrepClient({
3653
- apiKey: config.apiKey,
3885
+ morphApiKey: config.apiKey,
3654
3886
  debug: config.debug,
3655
3887
  timeout: config.timeout,
3656
3888
  retryConfig: config.retryConfig
@@ -3696,6 +3928,19 @@ var MorphClient = class {
3696
3928
  this.vercel = new VercelToolFactory(config);
3697
3929
  }
3698
3930
  };
3931
+
3932
+ // tools/warp_grep/gemini.ts
3933
+ var import_generative_ai = require("@google/generative-ai");
3934
+ var TOOL_PARAMETERS2 = {
3935
+ type: import_generative_ai.SchemaType.OBJECT,
3936
+ properties: {
3937
+ query: {
3938
+ type: import_generative_ai.SchemaType.STRING,
3939
+ description: "Free-form repository question"
3940
+ }
3941
+ },
3942
+ required: ["query"]
3943
+ };
3699
3944
  // Annotate the CommonJS export names for ESM import in node:
3700
3945
  0 && (module.exports = {
3701
3946
  AnthropicRouter,