@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
@@ -30,7 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // tools/warp_grep/openai.ts
31
31
  var openai_exports = {};
32
32
  __export(openai_exports, {
33
- createMorphWarpGrepTool: () => createMorphWarpGrepTool,
33
+ createWarpGrepTool: () => createWarpGrepTool,
34
34
  default: () => openai_default,
35
35
  execute: () => execute,
36
36
  formatResult: () => formatResult,
@@ -41,9 +41,13 @@ module.exports = __toCommonJS(openai_exports);
41
41
 
42
42
  // tools/warp_grep/agent/config.ts
43
43
  var AGENT_CONFIG = {
44
- // Give the model freedom; failsafe cap to prevent infinite loops
45
- MAX_ROUNDS: 10,
46
- TIMEOUT_MS: 3e4
44
+ MAX_TURNS: 4,
45
+ TIMEOUT_MS: 3e4,
46
+ MAX_CONTEXT_CHARS: 54e4,
47
+ MAX_OUTPUT_LINES: 200,
48
+ MAX_READ_LINES: 800,
49
+ MAX_LIST_DEPTH: 3,
50
+ LIST_TIMEOUT_MS: 2e3
47
51
  };
48
52
  var BUILTIN_EXCLUDES = [
49
53
  // Version control
@@ -125,113 +129,191 @@ var BUILTIN_EXCLUDES = [
125
129
  ".*"
126
130
  ];
127
131
  var DEFAULT_EXCLUDES = (process.env.MORPH_WARP_GREP_EXCLUDE || "").split(",").map((s) => s.trim()).filter(Boolean).concat(BUILTIN_EXCLUDES);
128
- var DEFAULT_MODEL = "morph-warp-grep";
132
+ var DEFAULT_MODEL = "morph-warp-grep-v1";
129
133
 
130
134
  // tools/warp_grep/agent/prompt.ts
131
- var SYSTEM_PROMPT = `You are a code search agent. Your task is to find all relevant code for a given query.
135
+ var SYSTEM_PROMPT = `You are a code search agent. Your task is to find all relevant code for a given search_string.
132
136
 
133
- <workflow>
137
+ ### workflow
134
138
  You have exactly 4 turns. The 4th turn MUST be a \`finish\` call. Each turn allows up to 8 parallel tool calls.
135
139
 
136
- - Turn 1: Map the territory OR dive deep (based on query specificity)
140
+ - Turn 1: Map the territory OR dive deep (based on search_string specificity)
137
141
  - Turn 2-3: Refine based on findings
138
142
  - Turn 4: MUST call \`finish\` with all relevant code locations
139
143
  - You MAY call \`finish\` early if confident\u2014but never before at least 1 search turn.
144
+ - The user strongly prefers if you can call the finish tool early, but you must be correct
140
145
 
141
- Remember, if the task feels easy to you, it is strongly desirable to call \`finish\` early using fewer turns, but quality over speed.
142
- </workflow>
146
+ Remember, if the task feels easy to you, it is strongly desirable to call 'finish' early using fewer turns, but quality over speed
143
147
 
144
- <tools>
145
- ### \`analyse <path> [pattern]\`
146
- Directory tree or file search. Shows structure of a path, optionally filtered by regex pattern.
147
- - \`path\`: Required. Directory or file path (use \`.\` for repo root)
148
- - \`pattern\`: Optional regex to filter results
148
+ ### tools
149
+ Tool calls use nested XML elements:
150
+ \`\`\`xml
151
+ <tool_name>
152
+ <parameter>value</parameter>
153
+ </tool_name>
154
+ \`\`\`
155
+
156
+ ### \`list_directory\`
157
+ Directory tree view. Shows structure of a path, optionally filtered by regex pattern.
158
+
159
+ Elements:
160
+ - \`<path>\` (required): Directory path to list (use \`.\` for repo root)
161
+ - \`<pattern>\` (optional): Regex to filter results
149
162
 
150
163
  Examples:
151
164
  \`\`\`
152
- analyse .
153
- analyse src/api
154
- analyse . ".*\\.ts$"
155
- analyse src "test.*"
165
+ <list_directory>
166
+ <path>src/services</path>
167
+ </list_directory>
168
+
169
+ <list_directory>
170
+ <path>lib/utils</path>
171
+ <pattern>.*\\.(ts|js)$</pattern>
172
+ </list_directory>
156
173
  \`\`\`
157
174
 
158
- ### \`read <path>[:start-end]\`
159
- Read file contents. Line range is 1-based, inclusive.
175
+ ### \`read\`
176
+ Read file contents. Supports multiple line ranges.
160
177
  - Returns numbered lines for easy reference
161
- - Omit range to read entire file
178
+ - ALWAYS include import statements (usually lines 1-20). Better to over-include than miss context.
179
+
180
+ Elements:
181
+ - \`<path>\` (required): File path to read
182
+ - \`<lines>\` (optional): Line ranges like "1-50,75-80,100-120" (omit to read entire file)
162
183
 
163
184
  Examples:
164
185
  \`\`\`
165
- read src/main.py
166
- read src/db/conn.py:10-50
167
- read package.json:1-20
186
+ <read>
187
+ <path>src/main.py</path>
188
+ </read>
189
+
190
+ <read>
191
+ <path>src/auth.py</path>
192
+ <lines>1-20,45-80,150-200</lines>
193
+ </read>
168
194
  \`\`\`
169
195
 
170
- ### \`grep '<pattern>' <path>\`
171
- Ripgrep search. Finds pattern matches across files.
172
- - \`'<pattern>'\`: Required. Regex pattern wrapped in single quotes
173
- - \`<path>\`: Required. Directory or file to search (use \`.\` for repo root)
196
+ ### \`grep\`
197
+ Search for pattern matches across files. Returns matches with 1 line of context above and below.
198
+ - Match lines use \`:\` separator \u2192 \`filepath:linenum:content\`
199
+ - Context lines use \`-\` separator \u2192 \`filepath-linenum-content\`
200
+
201
+ Elements:
202
+ - \`<pattern>\` (required): Search pattern (regex). Use \`(a|b)\` for OR patterns.
203
+ - \`<sub_dir>\` (optional): Subdirectory to search in (defaults to \`.\`)
204
+ - \`<glob>\` (optional): File pattern filter like \`*.py\` or \`*.{ts,tsx}\`
174
205
 
175
206
  Examples:
176
207
  \`\`\`
177
- grep 'class.*Service' src/
178
- grep 'def authenticate' .
179
- grep 'import.*from' src/components/
180
- grep 'TODO' .
208
+ <grep>
209
+ <pattern>(authenticate|authorize|login)</pattern>
210
+ <sub_dir>src/auth/</sub_dir>
211
+ </grep>
212
+
213
+ <grep>
214
+ <pattern>class.*(Service|Controller)</pattern>
215
+ <glob>*.{ts,js}</glob>
216
+ </grep>
217
+
218
+ <grep>
219
+ <pattern>(DB_HOST|DATABASE_URL|connection)</pattern>
220
+ <glob>*.{py,yaml,env}</glob>
221
+ <sub_dir>lib/</sub_dir>
222
+ </grep>
181
223
  \`\`\`
182
224
 
183
- ### \`finish <file1:ranges> [file2:ranges ...]\`
184
- Submit final answer with all relevant code locations.
185
- - Include generous line ranges\u2014don't be stingy with context
186
- - Ranges are comma-separated: \`file.py:10-30,50-60\`
187
- - ALWAYS include import statements at the top of files (usually lines 1-20)
188
- - If code spans multiple files, include ALL of them
189
- - Small files can be returned in full
225
+ ### \`finish\`
226
+ Submit final answer with all relevant code locations. Uses nested \`<file>\` elements.
227
+
228
+ File elements:
229
+ - \`<path>\` (required): File path
230
+ - \`<lines>\` (optional): Line ranges like "1-50,75-80" (\`*\` for entire file)
231
+
232
+ ALWAYS include import statements (usually lines 1-20). Better to over-include than miss context.
190
233
 
191
234
  Examples:
192
235
  \`\`\`
193
- finish src/auth.py:1-15,25-50,75-80 src/models/user.py:1-10,20-45
194
- finish src/index.ts:1-100
236
+ <finish>
237
+ <file>
238
+ <path>src/auth.py</path>
239
+ <lines>1-15,25-50,75-80</lines>
240
+ </file>
241
+ <file>
242
+ <path>src/models/user.py</path>
243
+ <lines>*</lines>
244
+ </file>
245
+ </finish>
195
246
  \`\`\`
196
247
  </tools>
197
248
 
198
249
  <strategy>
199
- **Before your first tool call, classify the query:**
250
+ **Before your first tool call, classify the search_string:**
200
251
 
201
- | Query Type | Turn 1 Strategy | Early Finish? |
202
- |------------|-----------------|---------------|
203
- | **Specific** (function name, error string, unique identifier) | 8 parallel greps on likely paths | Often by turn 2 |
204
- | **Conceptual** (how does X work, where is Y handled) | analyse + 2-3 broad greps | Rarely early |
205
- | **Exploratory** (find all tests, list API endpoints) | analyse at multiple depths | Usually needs 3 turns |
252
+ | Search_string Type | Round 1 Strategy | Early Finish? |
253
+ |------------|------------------|---------------|
254
+ | **Specific** (function name, error string, unique identifier) | 8 parallel greps on likely paths | Often by round 2 |
255
+ | **Conceptual** (how does X work, where is Y handled) | list_directory + 2-3 broad greps | Rarely early |
256
+ | **Exploratory** (find all tests, list API endpoints) | list_directory at multiple depths | Usually needs 3 rounds |
206
257
 
207
258
  **Parallel call patterns:**
208
259
  - **Shotgun grep**: Same pattern, 8 different directories\u2014fast coverage
209
260
  - **Variant grep**: 8 pattern variations (synonyms, naming conventions)\u2014catches inconsistent codebases
210
- - **Funnel**: 1 analyse + 7 greps\u2014orient and search simultaneously
261
+ - **Funnel**: 1 list_directory + 7 greps\u2014orient and search simultaneously
211
262
  - **Deep read**: 8 reads on files you already identified\u2014gather full context fast
263
+
264
+ **Tool call expectations:**
265
+ - 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.
266
+ - 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.
267
+ - 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.
268
+ - 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.
212
269
  </strategy>
213
270
 
214
271
  <output_format>
215
272
  EVERY response MUST follow this exact format:
216
273
 
217
274
  1. First, wrap your reasoning in \`<think>...</think>\` tags containing:
218
- - Query classification (specific/conceptual/exploratory)
219
- - Confidence estimate (can I finish in 1-2 turns?)
220
- - This turn's parallel strategy
275
+ - Search_string classification (specific/conceptual/exploratory)
276
+ - Confidence estimate (can I finish in 1-2 rounds?)
277
+ - This round's parallel strategy
221
278
  - What signals would let me finish early?
222
279
 
223
- 2. Then, output tool calls wrapped in \`<tool_call>...</tool_call>\` tags, one per line.
280
+ 2. Then, output up to 8 tool calls using nested XML elements.
224
281
 
225
282
  Example:
226
283
  \`\`\`
227
284
  <think>
228
- This is a specific query about authentication. I'll grep for auth-related patterns.
229
- High confidence I can finish in 2 turns if I find the auth module.
285
+ This is a specific search_string about authentication. I'll grep for auth-related patterns.
286
+ 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
230
287
  Strategy: Shotgun grep across likely directories.
231
288
  </think>
232
- <tool_call>grep 'authenticate' src/</tool_call>
233
- <tool_call>grep 'login' src/</tool_call>
234
- <tool_call>analyse src/auth</tool_call>
289
+ <grep>
290
+ <pattern>(authenticate|login|session)</pattern>
291
+ <sub_dir>src/auth/</sub_dir>
292
+ </grep>
293
+ <grep>
294
+ <pattern>(middleware|interceptor)</pattern>
295
+ <glob>*.{ts,js}</glob>
296
+ </grep>
297
+ <list_directory>
298
+ <path>src/auth</path>
299
+ </list_directory>
300
+ \`\`\`
301
+
302
+ Finishing example:
303
+ \`\`\`
304
+ <think>
305
+ I think I have a rough idea, but this is my last turn so I must call the finish tool regardless.
306
+ </think>
307
+ <finish>
308
+ <file>
309
+ <path>src/auth/login.py</path>
310
+ <lines>1-50</lines>
311
+ </file>
312
+ <file>
313
+ <path>src/middleware/session.py</path>
314
+ <lines>10-80</lines>
315
+ </file>
316
+ </finish>
235
317
  \`\`\`
236
318
 
237
319
  No commentary outside \`<think>\`. No explanations after tool calls.
@@ -244,17 +326,111 @@ When calling \`finish\`:
244
326
  - Include any type definitions, interfaces, or constants used
245
327
  - Better to over-include than leave the user missing context
246
328
  - If unsure about boundaries, include more rather than less
247
- </finishing_requirements>
248
-
249
- Begin your exploration now to find code relevant to the query.`;
329
+ </finishing_requirements>`;
250
330
  function getSystemPrompt() {
251
331
  return SYSTEM_PROMPT;
252
332
  }
253
333
 
254
334
  // tools/warp_grep/agent/parser.ts
255
- var VALID_COMMANDS = ["analyse", "grep", "read", "finish"];
335
+ var VALID_COMMANDS = ["list_directory", "grep", "read", "finish"];
336
+ function isValidCommand(name) {
337
+ return VALID_COMMANDS.includes(name);
338
+ }
339
+ function getXmlElementText(xml, tagName) {
340
+ const regex = new RegExp(`<${tagName}>([\\s\\S]*?)</${tagName}>`, "i");
341
+ const match = xml.match(regex);
342
+ return match ? match[1].trim() : null;
343
+ }
344
+ function parseNestedXmlTools(text) {
345
+ const tools = [];
346
+ const toolRegex = /<([a-z_][a-z0-9_]*)>([\s\S]*?)<\/\1>/gi;
347
+ let match;
348
+ while ((match = toolRegex.exec(text)) !== null) {
349
+ const rawToolName = match[1].toLowerCase();
350
+ const content = match[2];
351
+ if (!isValidCommand(rawToolName)) continue;
352
+ const toolName = rawToolName;
353
+ if (toolName === "list_directory") {
354
+ const path5 = getXmlElementText(content, "path");
355
+ const pattern = getXmlElementText(content, "pattern");
356
+ if (path5) {
357
+ tools.push({ name: "list_directory", arguments: { path: path5, pattern } });
358
+ }
359
+ } else if (toolName === "grep") {
360
+ const pattern = getXmlElementText(content, "pattern");
361
+ const subDir = getXmlElementText(content, "sub_dir");
362
+ const glob = getXmlElementText(content, "glob");
363
+ if (pattern) {
364
+ tools.push({
365
+ name: "grep",
366
+ arguments: {
367
+ pattern,
368
+ path: subDir || ".",
369
+ ...glob && { glob }
370
+ }
371
+ });
372
+ }
373
+ } else if (toolName === "read") {
374
+ const path5 = getXmlElementText(content, "path");
375
+ const linesStr = getXmlElementText(content, "lines");
376
+ if (path5) {
377
+ const args = { path: path5 };
378
+ if (linesStr) {
379
+ const ranges = [];
380
+ for (const rangeStr of linesStr.split(",")) {
381
+ const trimmed = rangeStr.trim();
382
+ if (!trimmed) continue;
383
+ const [s, e] = trimmed.split("-").map((v) => parseInt(v.trim(), 10));
384
+ if (Number.isFinite(s) && Number.isFinite(e)) {
385
+ ranges.push([s, e]);
386
+ } else if (Number.isFinite(s)) {
387
+ ranges.push([s, s]);
388
+ }
389
+ }
390
+ if (ranges.length === 1) {
391
+ args.start = ranges[0][0];
392
+ args.end = ranges[0][1];
393
+ } else if (ranges.length > 1) {
394
+ args.lines = ranges;
395
+ }
396
+ }
397
+ tools.push({ name: "read", arguments: args });
398
+ }
399
+ } else if (toolName === "finish") {
400
+ const fileRegex = /<file>([\s\S]*?)<\/file>/gi;
401
+ const files = [];
402
+ let fileMatch;
403
+ while ((fileMatch = fileRegex.exec(content)) !== null) {
404
+ const fileContent = fileMatch[1];
405
+ const filePath = getXmlElementText(fileContent, "path");
406
+ const linesStr = getXmlElementText(fileContent, "lines");
407
+ if (filePath && linesStr) {
408
+ const ranges = [];
409
+ for (const rangeStr of linesStr.split(",")) {
410
+ if (rangeStr.trim() === "*") {
411
+ ranges.push([1, 999999]);
412
+ } else {
413
+ const [s, e] = rangeStr.split("-").map((v) => parseInt(v.trim(), 10));
414
+ if (Number.isFinite(s) && Number.isFinite(e)) {
415
+ ranges.push([s, e]);
416
+ }
417
+ }
418
+ }
419
+ if (ranges.length > 0) {
420
+ files.push({ path: filePath, lines: ranges });
421
+ }
422
+ }
423
+ }
424
+ if (files.length > 0) {
425
+ tools.push({ name: "finish", arguments: { files } });
426
+ }
427
+ }
428
+ }
429
+ return tools;
430
+ }
256
431
  function preprocessText(text) {
257
432
  let processed = text.replace(/<think>[\s\S]*?<\/think>/gi, "");
433
+ const nestedTools = parseNestedXmlTools(processed);
258
434
  const openingTagRegex = /<tool_call>|<tool>/gi;
259
435
  const closingTagRegex = /<\/tool_call>|<\/tool>/gi;
260
436
  const openingMatches = processed.match(openingTagRegex) || [];
@@ -291,7 +467,7 @@ function preprocessText(text) {
291
467
  }
292
468
  }
293
469
  }
294
- return toolCallLines;
470
+ return { lines: toolCallLines, nestedTools };
295
471
  }
296
472
  var LLMResponseParser = class {
297
473
  finishSpecSplitRe = /,(?=[^,\s]+:)/;
@@ -299,8 +475,8 @@ var LLMResponseParser = class {
299
475
  if (typeof text !== "string") {
300
476
  throw new TypeError("Command text must be a string.");
301
477
  }
302
- const lines = preprocessText(text);
303
- const commands = [];
478
+ const { lines, nestedTools } = preprocessText(text);
479
+ const commands = [...nestedTools];
304
480
  let finishAccumulator = null;
305
481
  lines.forEach((line) => {
306
482
  if (!line || line.startsWith("#")) return;
@@ -308,8 +484,8 @@ var LLMResponseParser = class {
308
484
  if (parts.length === 0) return;
309
485
  const cmd = parts[0];
310
486
  switch (cmd) {
311
- case "analyse":
312
- this.handleAnalyse(parts, line, commands);
487
+ case "list_directory":
488
+ this.handleListDirectory(parts, line, commands);
313
489
  break;
314
490
  case "grep":
315
491
  this.handleGrep(parts, line, commands);
@@ -327,8 +503,8 @@ var LLMResponseParser = class {
327
503
  if (finishAccumulator) {
328
504
  const map = finishAccumulator;
329
505
  const entries = [...map.entries()];
330
- const filesPayload = entries.map(([path4, ranges]) => ({
331
- path: path4,
506
+ const filesPayload = entries.map(([path5, ranges]) => ({
507
+ path: path5,
332
508
  lines: [...ranges].sort((a, b) => a[0] - b[0])
333
509
  }));
334
510
  commands.push({ name: "finish", arguments: { files: filesPayload } });
@@ -360,18 +536,17 @@ var LLMResponseParser = class {
360
536
  skip(message) {
361
537
  return { name: "_skip", arguments: { message } };
362
538
  }
363
- handleAnalyse(parts, rawLine, commands) {
539
+ handleListDirectory(parts, rawLine, commands) {
364
540
  if (parts.length < 2) {
365
541
  commands.push(this.skip(
366
- `[SKIPPED] Your command "${rawLine}" is missing a path. Correct format: analyse <path> [pattern]. Example: analyse src/`
542
+ `[SKIPPED] Your command "${rawLine}" is missing a path. Correct format: list_directory <path> [pattern]. Example: list_directory src/`
367
543
  ));
368
544
  return;
369
545
  }
370
- const path4 = parts[1];
546
+ const path5 = parts[1];
371
547
  const pattern = parts[2]?.replace(/^"|"$/g, "") ?? null;
372
- commands.push({ name: "analyse", arguments: { path: path4, pattern } });
548
+ commands.push({ name: "list_directory", arguments: { path: path5, pattern } });
373
549
  }
374
- // no glob tool in MCP
375
550
  handleGrep(parts, rawLine, commands) {
376
551
  if (parts.length < 3) {
377
552
  commands.push(this.skip(
@@ -442,8 +617,30 @@ var LLMResponseParser = class {
442
617
  }
443
618
  };
444
619
 
445
- // tools/warp_grep/tools/read.ts
620
+ // tools/warp_grep/agent/tools/grep.ts
621
+ async function toolGrep(provider, args) {
622
+ const res = await provider.grep(args);
623
+ if (res.error) {
624
+ return { output: res.error };
625
+ }
626
+ if (!res.lines.length) {
627
+ return { output: "no matches" };
628
+ }
629
+ return { output: res.lines.join("\n") };
630
+ }
631
+
632
+ // tools/warp_grep/agent/tools/read.ts
446
633
  async function toolRead(provider, args) {
634
+ if (args.lines && args.lines.length > 0) {
635
+ const chunks = [];
636
+ for (const [start, end] of args.lines) {
637
+ const res2 = await provider.read({ path: args.path, start, end });
638
+ if (res2.error) return res2.error;
639
+ chunks.push(res2.lines.join("\n"));
640
+ }
641
+ if (chunks.every((c) => c === "")) return "(empty file)";
642
+ return chunks.join("\n...\n");
643
+ }
447
644
  const res = await provider.read({ path: args.path, start: args.start, end: args.end });
448
645
  if (res.error) {
449
646
  return res.error;
@@ -452,16 +649,56 @@ async function toolRead(provider, args) {
452
649
  return res.lines.join("\n");
453
650
  }
454
651
 
455
- // tools/warp_grep/tools/analyse.ts
456
- async function toolAnalyse(provider, args) {
457
- const list = await provider.analyse({
652
+ // tools/warp_grep/agent/tools/list_directory.ts
653
+ async function toolListDirectory(provider, args) {
654
+ const list = await provider.listDirectory({
458
655
  path: args.path,
459
656
  pattern: args.pattern ?? null,
460
- maxResults: args.maxResults ?? 100,
461
- maxDepth: args.maxDepth ?? 2
657
+ maxResults: args.maxResults ?? AGENT_CONFIG.MAX_OUTPUT_LINES,
658
+ maxDepth: args.maxDepth ?? AGENT_CONFIG.MAX_LIST_DEPTH
462
659
  });
463
660
  if (!list.length) return "empty";
464
- return list.map((e) => `${" ".repeat(e.depth)}- ${e.type === "dir" ? "[D]" : "[F]"} ${e.name}`).join("\n");
661
+ if (list.length >= AGENT_CONFIG.MAX_OUTPUT_LINES) {
662
+ return "query not specific enough, tool called tried to return too much context and failed";
663
+ }
664
+ return list.map((e) => {
665
+ const indent = " ".repeat(e.depth);
666
+ const name = e.type === "dir" ? `${e.name}/` : e.name;
667
+ return `${indent}${name}`;
668
+ }).join("\n");
669
+ }
670
+
671
+ // tools/warp_grep/agent/tools/finish.ts
672
+ async function readFinishFiles(repoRoot, files, reader) {
673
+ const out = [];
674
+ for (const f of files) {
675
+ const ranges = mergeRanges(f.lines);
676
+ const chunks = [];
677
+ for (const [s, e] of ranges) {
678
+ const lines = await reader(f.path, s, e);
679
+ chunks.push(lines.join("\n"));
680
+ }
681
+ out.push({ path: f.path, ranges, content: chunks.join("\n") });
682
+ }
683
+ return out;
684
+ }
685
+ function mergeRanges(ranges) {
686
+ if (!ranges.length) return [];
687
+ const sorted = [...ranges].sort((a, b) => a[0] - b[0]);
688
+ const merged = [];
689
+ let [cs, ce] = sorted[0];
690
+ for (let i = 1; i < sorted.length; i++) {
691
+ const [s, e] = sorted[i];
692
+ if (s <= ce + 1) {
693
+ ce = Math.max(ce, e);
694
+ } else {
695
+ merged.push([cs, ce]);
696
+ cs = s;
697
+ ce = e;
698
+ }
699
+ }
700
+ merged.push([cs, ce]);
701
+ return merged;
465
702
  }
466
703
 
467
704
  // tools/utils/resilience.ts
@@ -554,8 +791,8 @@ var ToolOutputFormatter = class {
554
791
  switch (name) {
555
792
  case "read":
556
793
  return this.formatRead(safeArgs, payload, isError);
557
- case "analyse":
558
- return this.formatAnalyse(safeArgs, payload, isError);
794
+ case "list_directory":
795
+ return this.formatListDirectory(safeArgs, payload, isError);
559
796
  case "grep":
560
797
  return this.formatGrep(safeArgs, payload, isError);
561
798
  default:
@@ -568,39 +805,56 @@ ${payload}
568
805
  if (isError) {
569
806
  return payload;
570
807
  }
571
- const path4 = this.asString(args.path) || "...";
572
- return `<file path="${path4}">
808
+ const path5 = this.asString(args.path) || "...";
809
+ const start = args.start;
810
+ const end = args.end;
811
+ const linesArray = args.lines;
812
+ const attributes = [`path="${path5}"`];
813
+ if (linesArray && linesArray.length > 0) {
814
+ const rangeStr = linesArray.map(([s, e]) => `${s}-${e}`).join(",");
815
+ attributes.push(`lines="${rangeStr}"`);
816
+ } else if (start !== void 0 && end !== void 0) {
817
+ attributes.push(`lines="${start}-${end}"`);
818
+ }
819
+ return `<read ${attributes.join(" ")}>
573
820
  ${payload}
574
- </file>`;
821
+ </read>`;
575
822
  }
576
- formatAnalyse(args, payload, isError) {
577
- const path4 = this.asString(args.path) || ".";
823
+ formatListDirectory(args, payload, isError) {
824
+ const path5 = this.asString(args.path) || ".";
825
+ const pattern = this.asString(args.pattern);
826
+ const attributes = [`path="${path5}"`];
827
+ if (pattern) {
828
+ attributes.push(`pattern="${pattern}"`);
829
+ }
578
830
  if (isError) {
579
- return `<analyse_results path="${path4}" status="error">
580
- ${payload}
581
- </analyse_results>`;
831
+ attributes.push('status="error"');
582
832
  }
583
- return `<analyse_results path="${path4}">
833
+ return `<list_directory ${attributes.join(" ")}>
584
834
  ${payload}
585
- </analyse_results>`;
835
+ </list_directory>`;
586
836
  }
587
837
  formatGrep(args, payload, isError) {
588
838
  const pattern = this.asString(args.pattern);
589
- const path4 = this.asString(args.path);
839
+ const subDir = this.asString(args.path);
840
+ const glob = this.asString(args.glob);
590
841
  const attributes = [];
591
842
  if (pattern !== void 0) {
592
843
  attributes.push(`pattern="${pattern}"`);
593
844
  }
594
- if (path4 !== void 0) {
595
- attributes.push(`path="${path4}"`);
845
+ if (subDir !== void 0) {
846
+ attributes.push(`sub_dir="${subDir}"`);
847
+ }
848
+ if (glob !== void 0) {
849
+ attributes.push(`glob="${glob}"`);
596
850
  }
597
851
  if (isError) {
598
852
  attributes.push('status="error"');
599
853
  }
600
854
  const attrText = attributes.length ? ` ${attributes.join(" ")}` : "";
601
- return `<grep_output${attrText}>
855
+ return `<grep${attrText}>
602
856
  ${payload}
603
- </grep_output>`;
857
+ </grep>`;
604
858
  }
605
859
  asString(value) {
606
860
  if (value === null || value === void 0) {
@@ -614,180 +868,100 @@ function formatAgentToolOutput(toolName, args, output, options = {}) {
614
868
  return sharedFormatter.format(toolName, args, output, options);
615
869
  }
616
870
 
617
- // tools/warp_grep/agent/grep_helpers.ts
618
- var GrepState = class {
619
- seenLines = /* @__PURE__ */ new Set();
620
- isNew(path4, lineNumber) {
621
- const key = this.makeKey(path4, lineNumber);
622
- return !this.seenLines.has(key);
623
- }
624
- add(path4, lineNumber) {
625
- this.seenLines.add(this.makeKey(path4, lineNumber));
626
- }
627
- makeKey(path4, lineNumber) {
628
- return `${path4}:${lineNumber}`;
629
- }
630
- };
631
- var MAX_GREP_OUTPUT_CHARS_PER_TURN = 6e4;
632
- function extractMatchFields(payload) {
633
- const text = payload.replace(/\r?\n$/, "");
634
- if (!text || text.startsWith("[error]")) {
635
- return null;
636
- }
637
- const firstSep = text.indexOf(":");
638
- if (firstSep === -1) {
639
- return null;
640
- }
641
- let filePath = text.slice(0, firstSep).trim();
642
- if (!filePath) {
643
- return null;
644
- }
645
- if (filePath.startsWith("./") || filePath.startsWith(".\\")) {
646
- filePath = filePath.slice(2);
647
- }
648
- const remainder = text.slice(firstSep + 1);
649
- const secondSep = remainder.indexOf(":");
650
- if (secondSep === -1) {
651
- return null;
652
- }
653
- const linePart = remainder.slice(0, secondSep);
654
- const lineNumber = Number.parseInt(linePart, 10);
655
- if (!Number.isInteger(lineNumber) || lineNumber <= 0) {
656
- return null;
657
- }
658
- let contentSegment = remainder.slice(secondSep + 1);
659
- const columnSep = contentSegment.indexOf(":");
660
- if (columnSep !== -1 && /^\d+$/.test(contentSegment.slice(0, columnSep))) {
661
- contentSegment = contentSegment.slice(columnSep + 1);
662
- }
663
- const content = contentSegment.trim();
664
- if (!content) {
665
- return null;
871
+ // tools/warp_grep/agent/helpers.ts
872
+ var import_path = __toESM(require("path"), 1);
873
+ var TRUNCATED_MARKER = "[truncated for context limit]";
874
+ function formatTurnMessage(turnsUsed, maxTurns) {
875
+ const turnsRemaining = maxTurns - turnsUsed;
876
+ if (turnsRemaining === 1) {
877
+ return `
878
+ 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`;
666
879
  }
667
- return { path: filePath, lineNumber, content };
880
+ return `
881
+ You have used ${turnsUsed} turn${turnsUsed === 1 ? "" : "s"} and have ${turnsRemaining} remaining`;
668
882
  }
669
- function parseAndFilterGrepOutput(rawOutput, state) {
670
- const matches = [];
671
- if (typeof rawOutput !== "string" || !rawOutput.trim()) {
672
- return matches;
673
- }
674
- for (const line of rawOutput.split(/\r?\n/)) {
675
- const fields = extractMatchFields(line);
676
- if (!fields) {
677
- continue;
678
- }
679
- if (state.isNew(fields.path, fields.lineNumber)) {
680
- matches.push(fields);
681
- state.add(fields.path, fields.lineNumber);
682
- }
683
- }
684
- return matches;
883
+ function calculateContextBudget(messages) {
884
+ const totalChars = messages.reduce((sum, m) => sum + m.content.length, 0);
885
+ const maxChars = AGENT_CONFIG.MAX_CONTEXT_CHARS;
886
+ const percent = Math.round(totalChars / maxChars * 100);
887
+ const usedK = Math.round(totalChars / 1e3);
888
+ const maxK = Math.round(maxChars / 1e3);
889
+ return `<context_budget>${percent}% (${usedK}K/${maxK}K chars)</context_budget>`;
685
890
  }
686
- function truncateOutput(payload, maxChars) {
687
- if (payload.length <= maxChars) {
688
- return payload;
689
- }
690
- const note = "... (output truncated)";
691
- const available = maxChars - note.length - 1;
692
- if (available <= 0) {
693
- return note;
694
- }
695
- if (payload.length <= available) {
696
- return `${payload.slice(0, available).replace(/\n$/, "")}
697
- ${note}`;
891
+ async function buildInitialState(repoRoot, query, provider) {
892
+ try {
893
+ const entries = await provider.listDirectory({
894
+ path: ".",
895
+ maxResults: AGENT_CONFIG.MAX_OUTPUT_LINES,
896
+ maxDepth: 2
897
+ });
898
+ const treeLines = entries.map((e) => {
899
+ const indent = " ".repeat(e.depth);
900
+ const name = e.type === "dir" ? `${e.name}/` : e.name;
901
+ return `${indent}${name}`;
902
+ });
903
+ const repoName = import_path.default.basename(repoRoot);
904
+ const treeOutput = treeLines.length > 0 ? `${repoName}/
905
+ ${treeLines.join("\n")}` : `${repoName}/`;
906
+ return `<repo_structure>
907
+ ${treeOutput}
908
+ </repo_structure>
909
+
910
+ <search_string>
911
+ ${query}
912
+ </search_string>`;
913
+ } catch {
914
+ const repoName = import_path.default.basename(repoRoot);
915
+ return `<repo_structure>
916
+ ${repoName}/
917
+ </repo_structure>
918
+
919
+ <search_string>
920
+ ${query}
921
+ </search_string>`;
698
922
  }
699
- const core = payload.slice(0, Math.max(0, available - 1));
700
- const trimmed = core.replace(/\n$/, "").replace(/\s+$/, "");
701
- const snippet = trimmed ? `${trimmed}\u2026` : "\u2026";
702
- return `${snippet}
703
- ${note}`;
704
923
  }
705
- function formatTurnGrepOutput(matches, maxChars = MAX_GREP_OUTPUT_CHARS_PER_TURN) {
706
- if (!matches || matches.length === 0) {
707
- return "No new matches found.";
924
+ function enforceContextLimit(messages, maxChars = AGENT_CONFIG.MAX_CONTEXT_CHARS) {
925
+ const getTotalChars = () => messages.reduce((sum, m) => sum + m.content.length, 0);
926
+ if (getTotalChars() <= maxChars) {
927
+ return messages;
708
928
  }
709
- const matchesByFile = /* @__PURE__ */ new Map();
710
- for (const match of matches) {
711
- if (!matchesByFile.has(match.path)) {
712
- matchesByFile.set(match.path, []);
929
+ const userIndices = [];
930
+ let firstUserSkipped = false;
931
+ for (let i = 0; i < messages.length; i++) {
932
+ if (messages[i].role === "user") {
933
+ if (!firstUserSkipped) {
934
+ firstUserSkipped = true;
935
+ continue;
936
+ }
937
+ userIndices.push(i);
713
938
  }
714
- matchesByFile.get(match.path).push(match);
715
939
  }
716
- const lines = [];
717
- const sortedPaths = Array.from(matchesByFile.keys()).sort();
718
- sortedPaths.forEach((filePath, index) => {
719
- if (index > 0) {
720
- lines.push("");
721
- }
722
- lines.push(filePath);
723
- const sortedMatches = matchesByFile.get(filePath).slice().sort((a, b) => a.lineNumber - b.lineNumber);
724
- for (const match of sortedMatches) {
725
- lines.push(`${match.lineNumber}:${match.content}`);
940
+ for (const idx of userIndices) {
941
+ if (getTotalChars() <= maxChars) {
942
+ break;
726
943
  }
727
- });
728
- return truncateOutput(lines.join("\n"), maxChars);
729
- }
730
-
731
- // tools/warp_grep/tools/finish.ts
732
- async function readFinishFiles(repoRoot, files, reader) {
733
- const out = [];
734
- for (const f of files) {
735
- const ranges = mergeRanges(f.lines);
736
- const chunks = [];
737
- for (const [s, e] of ranges) {
738
- const lines = await reader(f.path, s, e);
739
- chunks.push(lines.join("\n"));
944
+ if (messages[idx].content !== TRUNCATED_MARKER) {
945
+ messages[idx] = { role: "user", content: TRUNCATED_MARKER };
740
946
  }
741
- out.push({ path: f.path, ranges, content: chunks.join("\n") });
742
947
  }
743
- return out;
744
- }
745
- function mergeRanges(ranges) {
746
- if (!ranges.length) return [];
747
- const sorted = [...ranges].sort((a, b) => a[0] - b[0]);
748
- const merged = [];
749
- let [cs, ce] = sorted[0];
750
- for (let i = 1; i < sorted.length; i++) {
751
- const [s, e] = sorted[i];
752
- if (s <= ce + 1) {
753
- ce = Math.max(ce, e);
754
- } else {
755
- merged.push([cs, ce]);
756
- cs = s;
757
- ce = e;
758
- }
759
- }
760
- merged.push([cs, ce]);
761
- return merged;
948
+ return messages;
762
949
  }
763
950
 
764
951
  // tools/warp_grep/agent/runner.ts
765
- var import_path = __toESM(require("path"), 1);
952
+ var import_path2 = __toESM(require("path"), 1);
766
953
  var parser = new LLMResponseParser();
767
- async function buildInitialState(repoRoot, query, provider) {
768
- try {
769
- const entries = await provider.analyse({ path: ".", maxResults: 100 });
770
- const dirs = entries.filter((e) => e.type === "dir").map((d) => d.name).slice(0, 50);
771
- const files = entries.filter((e) => e.type === "file").map((f) => f.name).slice(0, 50);
772
- const parts = [
773
- `<repo_root>${repoRoot}</repo_root>`,
774
- `<top_dirs>${dirs.join(", ")}</top_dirs>`,
775
- `<top_files>${files.join(", ")}</top_files>`
776
- ];
777
- return parts.join("\n");
778
- } catch {
779
- return `<repo_root>${repoRoot}</repo_root>`;
780
- }
781
- }
782
- async function callModel(messages, model, apiKey) {
783
- const api = "https://api.morphllm.com/v1/chat/completions";
954
+ var DEFAULT_API_URL = "https://api.morphllm.com";
955
+ async function callModel(messages, model, options = {}) {
956
+ const baseUrl = DEFAULT_API_URL;
957
+ const apiKey = options.morphApiKey || process.env.MORPH_API_KEY || "";
784
958
  const fetchPromise = fetchWithRetry(
785
- api,
959
+ `${baseUrl}/v1/chat/completions`,
786
960
  {
787
961
  method: "POST",
788
962
  headers: {
789
963
  "Content-Type": "application/json",
790
- Authorization: `Bearer ${apiKey || process.env.MORPH_API_KEY || ""}`
964
+ Authorization: `Bearer ${apiKey}`
791
965
  },
792
966
  body: JSON.stringify({
793
967
  model,
@@ -796,10 +970,15 @@ async function callModel(messages, model, apiKey) {
796
970
  messages
797
971
  })
798
972
  },
799
- {}
973
+ options.retryConfig
800
974
  );
801
975
  const resp = await withTimeout(fetchPromise, AGENT_CONFIG.TIMEOUT_MS, "morph-warp-grep request timed out");
802
976
  if (!resp.ok) {
977
+ if (resp.status === 404) {
978
+ throw new Error(
979
+ "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"
980
+ );
981
+ }
803
982
  const t = await resp.text();
804
983
  throw new Error(`morph-warp-grep error ${resp.status}: ${t}`);
805
984
  }
@@ -811,23 +990,24 @@ async function callModel(messages, model, apiKey) {
811
990
  return content;
812
991
  }
813
992
  async function runWarpGrep(config) {
814
- const repoRoot = import_path.default.resolve(config.repoRoot || process.cwd());
993
+ const repoRoot = import_path2.default.resolve(config.repoRoot || process.cwd());
815
994
  const messages = [];
816
- const systemMessage = { role: "system", content: getSystemPrompt() };
817
- messages.push(systemMessage);
818
- const queryContent = `<query>${config.query}</query>`;
819
- messages.push({ role: "user", content: queryContent });
995
+ messages.push({ role: "system", content: getSystemPrompt() });
820
996
  const initialState = await buildInitialState(repoRoot, config.query, config.provider);
821
997
  messages.push({ role: "user", content: initialState });
822
- const maxRounds = AGENT_CONFIG.MAX_ROUNDS;
998
+ const maxTurns = AGENT_CONFIG.MAX_TURNS;
823
999
  const model = config.model || DEFAULT_MODEL;
824
1000
  const provider = config.provider;
825
1001
  const errors = [];
826
- const grepState = new GrepState();
827
1002
  let finishMeta;
828
1003
  let terminationReason = "terminated";
829
- for (let round = 1; round <= maxRounds; round += 1) {
830
- const assistantContent = await callModel(messages, model, config.apiKey).catch((e) => {
1004
+ for (let turn = 1; turn <= maxTurns; turn += 1) {
1005
+ enforceContextLimit(messages);
1006
+ const assistantContent = await callModel(messages, model, {
1007
+ morphApiKey: config.morphApiKey,
1008
+ morphApiUrl: config.morphApiUrl,
1009
+ retryConfig: config.retryConfig
1010
+ }).catch((e) => {
831
1011
  errors.push({ message: e instanceof Error ? e.message : String(e) });
832
1012
  return "";
833
1013
  });
@@ -835,13 +1015,13 @@ async function runWarpGrep(config) {
835
1015
  messages.push({ role: "assistant", content: assistantContent });
836
1016
  const toolCalls = parser.parse(assistantContent);
837
1017
  if (toolCalls.length === 0) {
838
- errors.push({ message: "No tool calls produced by the model." });
1018
+ 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" });
839
1019
  terminationReason = "terminated";
840
1020
  break;
841
1021
  }
842
1022
  const finishCalls = toolCalls.filter((c) => c.name === "finish");
843
1023
  const grepCalls = toolCalls.filter((c) => c.name === "grep");
844
- const analyseCalls = toolCalls.filter((c) => c.name === "analyse");
1024
+ const listDirCalls = toolCalls.filter((c) => c.name === "list_directory");
845
1025
  const readCalls = toolCalls.filter((c) => c.name === "read");
846
1026
  const skipCalls = toolCalls.filter((c) => c.name === "_skip");
847
1027
  const formatted = [];
@@ -849,69 +1029,42 @@ async function runWarpGrep(config) {
849
1029
  const msg = c.arguments?.message || "Command skipped due to parsing error";
850
1030
  formatted.push(msg);
851
1031
  }
852
- const otherPromises = [];
853
- for (const c of analyseCalls) {
1032
+ const allPromises = [];
1033
+ for (const c of grepCalls) {
1034
+ const args = c.arguments ?? {};
1035
+ allPromises.push(
1036
+ toolGrep(provider, args).then(
1037
+ ({ output }) => formatAgentToolOutput("grep", args, output, { isError: false }),
1038
+ (err) => formatAgentToolOutput("grep", args, String(err), { isError: true })
1039
+ )
1040
+ );
1041
+ }
1042
+ for (const c of listDirCalls) {
854
1043
  const args = c.arguments ?? {};
855
- otherPromises.push(
856
- toolAnalyse(provider, args).then(
857
- (p) => formatAgentToolOutput("analyse", args, p, { isError: false }),
858
- (err) => formatAgentToolOutput("analyse", args, String(err), { isError: true })
1044
+ allPromises.push(
1045
+ toolListDirectory(provider, args).then(
1046
+ (p) => formatAgentToolOutput("list_directory", args, p, { isError: false }),
1047
+ (err) => formatAgentToolOutput("list_directory", args, String(err), { isError: true })
859
1048
  )
860
1049
  );
861
1050
  }
862
1051
  for (const c of readCalls) {
863
1052
  const args = c.arguments ?? {};
864
- otherPromises.push(
1053
+ allPromises.push(
865
1054
  toolRead(provider, args).then(
866
1055
  (p) => formatAgentToolOutput("read", args, p, { isError: false }),
867
1056
  (err) => formatAgentToolOutput("read", args, String(err), { isError: true })
868
1057
  )
869
1058
  );
870
1059
  }
871
- const otherResults = await Promise.all(otherPromises);
872
- formatted.push(...otherResults);
873
- for (const c of grepCalls) {
874
- const args = c.arguments ?? {};
875
- try {
876
- const grepRes = await provider.grep({ pattern: args.pattern, path: args.path });
877
- if (grepRes.error) {
878
- errors.push({ message: grepRes.error });
879
- terminationReason = "terminated";
880
- return {
881
- terminationReason: "terminated",
882
- messages,
883
- errors
884
- };
885
- }
886
- const rawOutput = Array.isArray(grepRes.lines) ? grepRes.lines.join("\n") : "";
887
- const newMatches = parseAndFilterGrepOutput(rawOutput, grepState);
888
- let formattedPayload = formatTurnGrepOutput(newMatches);
889
- if (formattedPayload === "No new matches found.") {
890
- formattedPayload = "no new matches";
891
- }
892
- formatted.push(formatAgentToolOutput("grep", args, formattedPayload, { isError: false }));
893
- } catch (err) {
894
- formatted.push(formatAgentToolOutput("grep", args, String(err), { isError: true }));
895
- }
1060
+ const allResults = await Promise.all(allPromises);
1061
+ for (const result of allResults) {
1062
+ formatted.push(result);
896
1063
  }
897
1064
  if (formatted.length > 0) {
898
- const turnsUsed = round;
899
- const turnsRemaining = 4 - turnsUsed;
900
- let turnMessage;
901
- if (turnsRemaining === 0) {
902
- turnMessage = `
903
-
904
- [Turn ${turnsUsed}/4] This is your LAST turn. You MUST call the finish tool now.`;
905
- } else if (turnsRemaining === 1) {
906
- turnMessage = `
907
-
908
- [Turn ${turnsUsed}/4] You have 1 turn remaining. Next turn you MUST call the finish tool.`;
909
- } else {
910
- turnMessage = `
911
-
912
- [Turn ${turnsUsed}/4] You have ${turnsRemaining} turns remaining.`;
913
- }
914
- messages.push({ role: "user", content: formatted.join("\n") + turnMessage });
1065
+ const turnMessage = formatTurnMessage(turn, maxTurns);
1066
+ const contextBudget = calculateContextBudget(messages);
1067
+ messages.push({ role: "user", content: formatted.join("\n") + turnMessage + "\n" + contextBudget });
915
1068
  }
916
1069
  if (finishCalls.length) {
917
1070
  const fc = finishCalls[0];
@@ -961,7 +1114,7 @@ async function runWarpGrep(config) {
961
1114
 
962
1115
  // tools/warp_grep/providers/local.ts
963
1116
  var import_promises2 = __toESM(require("fs/promises"), 1);
964
- var import_path3 = __toESM(require("path"), 1);
1117
+ var import_path4 = __toESM(require("path"), 1);
965
1118
 
966
1119
  // tools/warp_grep/utils/ripgrep.ts
967
1120
  var import_child_process = require("child_process");
@@ -1026,21 +1179,21 @@ async function runRipgrep(args, opts) {
1026
1179
 
1027
1180
  // tools/warp_grep/utils/paths.ts
1028
1181
  var import_fs = __toESM(require("fs"), 1);
1029
- var import_path2 = __toESM(require("path"), 1);
1182
+ var import_path3 = __toESM(require("path"), 1);
1030
1183
  function resolveUnderRepo(repoRoot, targetPath) {
1031
- const absRoot = import_path2.default.resolve(repoRoot);
1032
- const resolved = import_path2.default.resolve(absRoot, targetPath);
1184
+ const absRoot = import_path3.default.resolve(repoRoot);
1185
+ const resolved = import_path3.default.resolve(absRoot, targetPath);
1033
1186
  ensureWithinRepo(absRoot, resolved);
1034
1187
  return resolved;
1035
1188
  }
1036
1189
  function ensureWithinRepo(repoRoot, absTarget) {
1037
- const rel = import_path2.default.relative(import_path2.default.resolve(repoRoot), import_path2.default.resolve(absTarget));
1038
- if (rel.startsWith("..") || import_path2.default.isAbsolute(rel)) {
1190
+ const rel = import_path3.default.relative(import_path3.default.resolve(repoRoot), import_path3.default.resolve(absTarget));
1191
+ if (rel.startsWith("..") || import_path3.default.isAbsolute(rel)) {
1039
1192
  throw new Error(`Path outside repository root: ${absTarget}`);
1040
1193
  }
1041
1194
  }
1042
1195
  function toRepoRelative(repoRoot, absPath) {
1043
- return import_path2.default.relative(import_path2.default.resolve(repoRoot), import_path2.default.resolve(absPath));
1196
+ return import_path3.default.relative(import_path3.default.resolve(repoRoot), import_path3.default.resolve(absPath));
1044
1197
  }
1045
1198
  function isSymlink(p) {
1046
1199
  try {
@@ -1083,10 +1236,18 @@ var LocalRipgrepProvider = class {
1083
1236
  this.excludes = excludes;
1084
1237
  }
1085
1238
  async grep(params) {
1086
- const abs = resolveUnderRepo(this.repoRoot, params.path);
1239
+ let abs;
1240
+ try {
1241
+ abs = resolveUnderRepo(this.repoRoot, params.path);
1242
+ } catch (err) {
1243
+ return {
1244
+ lines: [],
1245
+ error: `[PATH ERROR] ${err instanceof Error ? err.message : String(err)}`
1246
+ };
1247
+ }
1087
1248
  const stat = await import_promises2.default.stat(abs).catch(() => null);
1088
1249
  if (!stat) return { lines: [] };
1089
- const targetArg = abs === import_path3.default.resolve(this.repoRoot) ? "." : toRepoRelative(this.repoRoot, abs);
1250
+ const targetArg = abs === import_path4.default.resolve(this.repoRoot) ? "." : toRepoRelative(this.repoRoot, abs);
1090
1251
  const args = [
1091
1252
  "--no-config",
1092
1253
  "--no-heading",
@@ -1095,6 +1256,9 @@ var LocalRipgrepProvider = class {
1095
1256
  "--color=never",
1096
1257
  "--trim",
1097
1258
  "--max-columns=400",
1259
+ "-C",
1260
+ "1",
1261
+ ...params.glob ? ["--glob", params.glob] : [],
1098
1262
  ...this.excludes.flatMap((e) => ["-g", `!${e}`]),
1099
1263
  params.pattern,
1100
1264
  targetArg || "."
@@ -1119,29 +1283,24 @@ Details: ${res.stderr}` : ""}`
1119
1283
  };
1120
1284
  }
1121
1285
  const lines = (res.stdout || "").trim().split(/\r?\n/).filter((l) => l.length > 0);
1122
- return { lines };
1123
- }
1124
- async glob(params) {
1125
- const abs = resolveUnderRepo(this.repoRoot, params.path);
1126
- const targetArg = abs === import_path3.default.resolve(this.repoRoot) ? "." : toRepoRelative(this.repoRoot, abs);
1127
- const args = [
1128
- "--no-config",
1129
- "--files",
1130
- "-g",
1131
- params.pattern,
1132
- ...this.excludes.flatMap((e) => ["-g", `!${e}`]),
1133
- targetArg || "."
1134
- ];
1135
- const res = await runRipgrep(args, { cwd: this.repoRoot });
1136
- if (res.exitCode === -1) {
1137
- console.warn(`[warp_grep] ripgrep not available for glob: ${res.stderr || "execution failed"}`);
1138
- return { files: [] };
1286
+ if (lines.length > AGENT_CONFIG.MAX_OUTPUT_LINES) {
1287
+ return {
1288
+ lines: [],
1289
+ error: "query not specific enough, tool tried to return too much context and failed"
1290
+ };
1139
1291
  }
1140
- const files = (res.stdout || "").trim().split(/\r?\n/).filter((l) => l.length > 0);
1141
- return { files };
1292
+ return { lines };
1142
1293
  }
1143
1294
  async read(params) {
1144
- const abs = resolveUnderRepo(this.repoRoot, params.path);
1295
+ let abs;
1296
+ try {
1297
+ abs = resolveUnderRepo(this.repoRoot, params.path);
1298
+ } catch (err) {
1299
+ return {
1300
+ lines: [],
1301
+ error: `[PATH ERROR] ${err instanceof Error ? err.message : String(err)}`
1302
+ };
1303
+ }
1145
1304
  const stat = await import_promises2.default.stat(abs).catch(() => null);
1146
1305
  if (!stat || !stat.isFile()) {
1147
1306
  return {
@@ -1161,7 +1320,15 @@ Details: ${res.stderr}` : ""}`
1161
1320
  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.`
1162
1321
  };
1163
1322
  }
1164
- const lines = await readAllLines(abs);
1323
+ let lines;
1324
+ try {
1325
+ lines = await readAllLines(abs);
1326
+ } catch (err) {
1327
+ return {
1328
+ lines: [],
1329
+ error: `[READ ERROR] Failed to read "${params.path}": ${err instanceof Error ? err.message : String(err)}`
1330
+ };
1331
+ }
1165
1332
  const total = lines.length;
1166
1333
  let s = params.start ?? 1;
1167
1334
  let e = Math.min(params.end ?? total, total);
@@ -1174,30 +1341,46 @@ Details: ${res.stderr}` : ""}`
1174
1341
  const content = lines[i - 1] ?? "";
1175
1342
  out.push(`${i}|${content}`);
1176
1343
  }
1344
+ if (out.length > AGENT_CONFIG.MAX_READ_LINES) {
1345
+ const truncated = out.slice(0, AGENT_CONFIG.MAX_READ_LINES);
1346
+ truncated.push(`... [truncated: showing ${AGENT_CONFIG.MAX_READ_LINES} of ${out.length} lines]`);
1347
+ return { lines: truncated };
1348
+ }
1177
1349
  return { lines: out };
1178
1350
  }
1179
- async analyse(params) {
1180
- const abs = resolveUnderRepo(this.repoRoot, params.path);
1351
+ async listDirectory(params) {
1352
+ let abs;
1353
+ try {
1354
+ abs = resolveUnderRepo(this.repoRoot, params.path);
1355
+ } catch {
1356
+ return [];
1357
+ }
1181
1358
  const stat = await import_promises2.default.stat(abs).catch(() => null);
1182
1359
  if (!stat || !stat.isDirectory()) {
1183
1360
  return [];
1184
1361
  }
1185
- const maxResults = params.maxResults ?? 100;
1186
- const maxDepth = params.maxDepth ?? 2;
1362
+ const maxResults = params.maxResults ?? AGENT_CONFIG.MAX_OUTPUT_LINES;
1363
+ const maxDepth = params.maxDepth ?? AGENT_CONFIG.MAX_LIST_DEPTH;
1187
1364
  const regex = params.pattern ? new RegExp(params.pattern) : null;
1188
1365
  const results = [];
1366
+ let timedOut = false;
1367
+ const startTime = Date.now();
1189
1368
  async function walk(dir, depth) {
1369
+ if (Date.now() - startTime > AGENT_CONFIG.LIST_TIMEOUT_MS) {
1370
+ timedOut = true;
1371
+ return;
1372
+ }
1190
1373
  if (depth > maxDepth || results.length >= maxResults) return;
1191
1374
  const entries = await import_promises2.default.readdir(dir, { withFileTypes: true });
1192
1375
  for (const entry of entries) {
1193
- const full = import_path3.default.join(dir, entry.name);
1376
+ if (timedOut || results.length >= maxResults) break;
1377
+ const full = import_path4.default.join(dir, entry.name);
1194
1378
  const rel = toRepoRelative(abs, full).replace(/^[.][/\\]?/, "");
1195
- if (DEFAULT_EXCLUDES.some((ex) => rel.split(import_path3.default.sep).includes(ex))) continue;
1379
+ if (DEFAULT_EXCLUDES.some((ex) => rel.split(import_path4.default.sep).includes(ex))) continue;
1196
1380
  if (regex && !regex.test(entry.name)) continue;
1197
- if (results.length >= maxResults) break;
1198
1381
  results.push({
1199
1382
  name: entry.name,
1200
- path: toRepoRelative(import_path3.default.resolve(""), full),
1383
+ path: toRepoRelative(import_path4.default.resolve(""), full),
1201
1384
  // relative display
1202
1385
  type: entry.isDirectory() ? "dir" : "file",
1203
1386
  depth
@@ -1212,11 +1395,121 @@ Details: ${res.stderr}` : ""}`
1212
1395
  }
1213
1396
  };
1214
1397
 
1215
- // tools/warp_grep/prompts.ts
1216
- var WARP_GREP_TOOL_NAME = "warpgrep_codebase_search";
1217
- var WARP_GREP_DESCRIPTION = 'IMPORTANT: If you need to explore the codebase to gather context, and the task does not involve a single file or function which is provided by name, you should ALWAYS use the warpgrep codebase search tool first instead of running search commands. When the task requires exploration beyond a single known file, invoke warpgrep first with a natural-language query describing the target functionality, bug, or architectural concern. Warp Grep is a fast and accurate tool that can search for all relevant context in a codebase. Keep queries action-oriented (e.g., "Find where billing invoices are generated and emailed"), and after reviewing the summarized results, you may verify important findings with other search tools or direct file reads to ensure completeness. Warp Grep can be used for query types like: find function responsible for <small feature>; find code that does <description>; find code path for <big feature>; Where does <minimal error message> come from?; or any query of that type. When a task requires exploration beyond a single known file, ALWAYS default to warpgrep codebase search before other search mechanisms.';
1398
+ // tools/warp_grep/providers/remote.ts
1399
+ var RemoteCommandsProvider = class {
1400
+ constructor(repoRoot, commands) {
1401
+ this.repoRoot = repoRoot;
1402
+ this.commands = commands;
1403
+ }
1404
+ /**
1405
+ * Run grep command and parse ripgrep output
1406
+ */
1407
+ async grep(params) {
1408
+ try {
1409
+ const stdout = await this.commands.grep(params.pattern, params.path, params.glob);
1410
+ const lines = (stdout || "").trim().split(/\r?\n/).filter((l) => l.length > 0);
1411
+ if (lines.length > AGENT_CONFIG.MAX_OUTPUT_LINES) {
1412
+ return {
1413
+ lines: [],
1414
+ error: "Query not specific enough - too many results returned. Try a more specific pattern."
1415
+ };
1416
+ }
1417
+ return { lines };
1418
+ } catch (error) {
1419
+ return {
1420
+ lines: [],
1421
+ error: `[GREP ERROR] ${error instanceof Error ? error.message : String(error)}`
1422
+ };
1423
+ }
1424
+ }
1425
+ /**
1426
+ * Read file and add line numbers
1427
+ */
1428
+ async read(params) {
1429
+ const start = params.start ?? 1;
1430
+ const end = params.end ?? 1e6;
1431
+ try {
1432
+ const stdout = await this.commands.read(params.path, start, end);
1433
+ const contentLines = (stdout || "").split("\n");
1434
+ if (contentLines.length > 0 && contentLines[contentLines.length - 1] === "") {
1435
+ contentLines.pop();
1436
+ }
1437
+ const lines = contentLines.map((content, idx) => `${start + idx}|${content}`);
1438
+ if (lines.length > AGENT_CONFIG.MAX_READ_LINES) {
1439
+ const truncated = lines.slice(0, AGENT_CONFIG.MAX_READ_LINES);
1440
+ truncated.push(`... [truncated: showing ${AGENT_CONFIG.MAX_READ_LINES} of ${lines.length} lines]`);
1441
+ return { lines: truncated };
1442
+ }
1443
+ return { lines };
1444
+ } catch (error) {
1445
+ return {
1446
+ lines: [],
1447
+ error: `[READ ERROR] ${error instanceof Error ? error.message : String(error)}`
1448
+ };
1449
+ }
1450
+ }
1451
+ /**
1452
+ * List directory and parse find output
1453
+ */
1454
+ async listDirectory(params) {
1455
+ const maxDepth = params.maxDepth ?? AGENT_CONFIG.MAX_LIST_DEPTH;
1456
+ const maxResults = params.maxResults ?? AGENT_CONFIG.MAX_OUTPUT_LINES;
1457
+ try {
1458
+ const stdout = await this.commands.listDir(params.path, maxDepth);
1459
+ const paths = (stdout || "").trim().split(/\r?\n/).filter((p) => p.length > 0);
1460
+ const regex = params.pattern ? new RegExp(params.pattern) : null;
1461
+ const entries = [];
1462
+ for (const fullPath of paths) {
1463
+ if (fullPath === params.path || fullPath === this.repoRoot) continue;
1464
+ const name = fullPath.split("/").pop() || "";
1465
+ if (regex && !regex.test(name)) continue;
1466
+ let relativePath = fullPath;
1467
+ if (fullPath.startsWith(this.repoRoot)) {
1468
+ relativePath = fullPath.slice(this.repoRoot.length).replace(/^\//, "");
1469
+ }
1470
+ const depth = relativePath.split("/").filter(Boolean).length - 1;
1471
+ const hasExtension = name.includes(".") && !name.startsWith(".");
1472
+ const type = hasExtension ? "file" : "dir";
1473
+ entries.push({
1474
+ name,
1475
+ path: relativePath,
1476
+ type,
1477
+ depth: Math.max(0, depth)
1478
+ });
1479
+ if (entries.length >= maxResults) break;
1480
+ }
1481
+ return entries;
1482
+ } catch (error) {
1483
+ return [];
1484
+ }
1485
+ }
1486
+ };
1218
1487
 
1219
- // tools/warp_grep/core.ts
1488
+ // tools/warp_grep/client.ts
1489
+ async function executeToolCall(input, config) {
1490
+ const parsed = typeof input === "string" ? JSON.parse(input) : input;
1491
+ const provider = config.remoteCommands ? new RemoteCommandsProvider(config.repoRoot, config.remoteCommands) : config.provider ?? new LocalRipgrepProvider(config.repoRoot, config.excludes);
1492
+ const result = await runWarpGrep({
1493
+ query: parsed.query,
1494
+ repoRoot: config.repoRoot,
1495
+ provider,
1496
+ excludes: config.excludes,
1497
+ includes: config.includes,
1498
+ debug: config.debug ?? false,
1499
+ morphApiKey: config.morphApiKey,
1500
+ morphApiUrl: config.morphApiUrl,
1501
+ retryConfig: config.retryConfig
1502
+ });
1503
+ const finish = result.finish;
1504
+ if (result.terminationReason !== "completed" || !finish?.metadata) {
1505
+ return { success: false, error: "Search did not complete" };
1506
+ }
1507
+ const contexts = (finish.resolved ?? []).map((r) => ({
1508
+ file: r.path,
1509
+ content: r.content
1510
+ }));
1511
+ return { success: true, contexts, summary: finish.payload };
1512
+ }
1220
1513
  function formatResult(result) {
1221
1514
  if (!result.success) {
1222
1515
  return `Search failed: ${result.error}`;
@@ -1240,6 +1533,10 @@ function formatResult(result) {
1240
1533
  return lines.join("\n");
1241
1534
  }
1242
1535
 
1536
+ // tools/warp_grep/prompts.ts
1537
+ var WARP_GREP_TOOL_NAME = "warpgrep_codebase_search";
1538
+ var WARP_GREP_DESCRIPTION = 'IMPORTANT: If you need to explore the codebase to gather context, and the task does not involve a single file or function which is provided by name, you should ALWAYS use the warpgrep codebase search tool first instead of running search commands. When the task requires exploration beyond a single known file, invoke warpgrep first with a natural-language query describing the target functionality, bug, or architectural concern. Warp Grep is a fast and accurate tool that can search for all relevant context in a codebase. Keep queries action-oriented (e.g., "Find where billing invoices are generated and emailed"), and after reviewing the summarized results, you may verify important findings with other search tools or direct file reads to ensure completeness. Warp Grep can be used for query types like: find function responsible for <small feature>; find code that does <description>; find code path for <big feature>; Where does <minimal error message> come from?; or any query of that type. When a task requires exploration beyond a single known file, ALWAYS default to warpgrep codebase search before other search mechanisms.';
1539
+
1243
1540
  // tools/warp_grep/openai.ts
1244
1541
  var TOOL_PARAMETERS = {
1245
1542
  type: "object",
@@ -1257,28 +1554,9 @@ var warpGrepTool = {
1257
1554
  }
1258
1555
  };
1259
1556
  async function execute(input, config) {
1260
- const parsed = typeof input === "string" ? JSON.parse(input) : input;
1261
- const provider = config.provider ?? new LocalRipgrepProvider(config.repoRoot, config.excludes);
1262
- const result = await runWarpGrep({
1263
- query: parsed.query,
1264
- repoRoot: config.repoRoot,
1265
- provider,
1266
- excludes: config.excludes,
1267
- includes: config.includes,
1268
- debug: config.debug ?? false,
1269
- apiKey: config.apiKey
1270
- });
1271
- const finish = result.finish;
1272
- if (result.terminationReason !== "completed" || !finish?.metadata) {
1273
- return { success: false, error: "Search did not complete" };
1274
- }
1275
- const contexts = (finish.resolved ?? []).map((r) => ({
1276
- file: r.path,
1277
- content: r.content
1278
- }));
1279
- return { success: true, contexts, summary: finish.payload };
1557
+ return executeToolCall(input, config);
1280
1558
  }
1281
- function createMorphWarpGrepTool(config) {
1559
+ function createWarpGrepTool(config) {
1282
1560
  const tool = {
1283
1561
  type: "function",
1284
1562
  function: {
@@ -1289,7 +1567,7 @@ function createMorphWarpGrepTool(config) {
1289
1567
  };
1290
1568
  return Object.assign(tool, {
1291
1569
  execute: async (input) => {
1292
- return execute(input, config);
1570
+ return executeToolCall(input, config);
1293
1571
  },
1294
1572
  formatResult: (result) => {
1295
1573
  return formatResult(result);
@@ -1302,7 +1580,7 @@ function createMorphWarpGrepTool(config) {
1302
1580
  var openai_default = warpGrepTool;
1303
1581
  // Annotate the CommonJS export names for ESM import in node:
1304
1582
  0 && (module.exports = {
1305
- createMorphWarpGrepTool,
1583
+ createWarpGrepTool,
1306
1584
  execute,
1307
1585
  formatResult,
1308
1586
  getSystemPrompt,