@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
@@ -31,25 +31,37 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var warp_grep_exports = {};
32
32
  __export(warp_grep_exports, {
33
33
  LocalRipgrepProvider: () => LocalRipgrepProvider,
34
+ RemoteCommandsProvider: () => RemoteCommandsProvider,
34
35
  WARP_GREP_DESCRIPTION: () => WARP_GREP_DESCRIPTION,
35
36
  WARP_GREP_SYSTEM_PROMPT: () => SYSTEM_PROMPT,
36
37
  WARP_GREP_TOOL_NAME: () => WARP_GREP_TOOL_NAME,
37
38
  WarpGrepClient: () => WarpGrepClient,
38
- createAnthropicWarpGrepTool: () => createMorphWarpGrepTool2,
39
- createOpenAIWarpGrepTool: () => createMorphWarpGrepTool,
40
- createVercelWarpGrepTool: () => createMorphWarpGrepTool3,
39
+ anthropic: () => anthropic_exports,
40
+ executeToolCall: () => executeToolCall,
41
41
  executeWarpGrep: () => executeWarpGrep,
42
42
  formatResult: () => formatResult,
43
+ gemini: () => gemini_exports,
43
44
  getSystemPrompt: () => getSystemPrompt,
44
- runWarpGrep: () => runWarpGrep
45
+ normalizeFinishFiles: () => normalizeFinishFiles,
46
+ openai: () => openai_exports,
47
+ readFinishFiles: () => readFinishFiles,
48
+ runWarpGrep: () => runWarpGrep,
49
+ toolGrep: () => toolGrep,
50
+ toolListDirectory: () => toolListDirectory,
51
+ toolRead: () => toolRead,
52
+ vercel: () => vercel_exports
45
53
  });
46
54
  module.exports = __toCommonJS(warp_grep_exports);
47
55
 
48
56
  // tools/warp_grep/agent/config.ts
49
57
  var AGENT_CONFIG = {
50
- // Give the model freedom; failsafe cap to prevent infinite loops
51
- MAX_ROUNDS: 10,
52
- TIMEOUT_MS: 3e4
58
+ MAX_TURNS: 4,
59
+ TIMEOUT_MS: 3e4,
60
+ MAX_CONTEXT_CHARS: 54e4,
61
+ MAX_OUTPUT_LINES: 200,
62
+ MAX_READ_LINES: 800,
63
+ MAX_LIST_DEPTH: 3,
64
+ LIST_TIMEOUT_MS: 2e3
53
65
  };
54
66
  var BUILTIN_EXCLUDES = [
55
67
  // Version control
@@ -131,113 +143,191 @@ var BUILTIN_EXCLUDES = [
131
143
  ".*"
132
144
  ];
133
145
  var DEFAULT_EXCLUDES = (process.env.MORPH_WARP_GREP_EXCLUDE || "").split(",").map((s) => s.trim()).filter(Boolean).concat(BUILTIN_EXCLUDES);
134
- var DEFAULT_MODEL = "morph-warp-grep";
146
+ var DEFAULT_MODEL = "morph-warp-grep-v1";
135
147
 
136
148
  // tools/warp_grep/agent/prompt.ts
137
- var SYSTEM_PROMPT = `You are a code search agent. Your task is to find all relevant code for a given query.
149
+ var SYSTEM_PROMPT = `You are a code search agent. Your task is to find all relevant code for a given search_string.
138
150
 
139
- <workflow>
151
+ ### workflow
140
152
  You have exactly 4 turns. The 4th turn MUST be a \`finish\` call. Each turn allows up to 8 parallel tool calls.
141
153
 
142
- - Turn 1: Map the territory OR dive deep (based on query specificity)
154
+ - Turn 1: Map the territory OR dive deep (based on search_string specificity)
143
155
  - Turn 2-3: Refine based on findings
144
156
  - Turn 4: MUST call \`finish\` with all relevant code locations
145
157
  - You MAY call \`finish\` early if confident\u2014but never before at least 1 search turn.
158
+ - The user strongly prefers if you can call the finish tool early, but you must be correct
146
159
 
147
- Remember, if the task feels easy to you, it is strongly desirable to call \`finish\` early using fewer turns, but quality over speed.
148
- </workflow>
160
+ Remember, if the task feels easy to you, it is strongly desirable to call 'finish' early using fewer turns, but quality over speed
149
161
 
150
- <tools>
151
- ### \`analyse <path> [pattern]\`
152
- Directory tree or file search. Shows structure of a path, optionally filtered by regex pattern.
153
- - \`path\`: Required. Directory or file path (use \`.\` for repo root)
154
- - \`pattern\`: Optional regex to filter results
162
+ ### tools
163
+ Tool calls use nested XML elements:
164
+ \`\`\`xml
165
+ <tool_name>
166
+ <parameter>value</parameter>
167
+ </tool_name>
168
+ \`\`\`
169
+
170
+ ### \`list_directory\`
171
+ Directory tree view. Shows structure of a path, optionally filtered by regex pattern.
172
+
173
+ Elements:
174
+ - \`<path>\` (required): Directory path to list (use \`.\` for repo root)
175
+ - \`<pattern>\` (optional): Regex to filter results
155
176
 
156
177
  Examples:
157
178
  \`\`\`
158
- analyse .
159
- analyse src/api
160
- analyse . ".*\\.ts$"
161
- analyse src "test.*"
179
+ <list_directory>
180
+ <path>src/services</path>
181
+ </list_directory>
182
+
183
+ <list_directory>
184
+ <path>lib/utils</path>
185
+ <pattern>.*\\.(ts|js)$</pattern>
186
+ </list_directory>
162
187
  \`\`\`
163
188
 
164
- ### \`read <path>[:start-end]\`
165
- Read file contents. Line range is 1-based, inclusive.
189
+ ### \`read\`
190
+ Read file contents. Supports multiple line ranges.
166
191
  - Returns numbered lines for easy reference
167
- - Omit range to read entire file
192
+ - ALWAYS include import statements (usually lines 1-20). Better to over-include than miss context.
193
+
194
+ Elements:
195
+ - \`<path>\` (required): File path to read
196
+ - \`<lines>\` (optional): Line ranges like "1-50,75-80,100-120" (omit to read entire file)
168
197
 
169
198
  Examples:
170
199
  \`\`\`
171
- read src/main.py
172
- read src/db/conn.py:10-50
173
- read package.json:1-20
200
+ <read>
201
+ <path>src/main.py</path>
202
+ </read>
203
+
204
+ <read>
205
+ <path>src/auth.py</path>
206
+ <lines>1-20,45-80,150-200</lines>
207
+ </read>
174
208
  \`\`\`
175
209
 
176
- ### \`grep '<pattern>' <path>\`
177
- Ripgrep search. Finds pattern matches across files.
178
- - \`'<pattern>'\`: Required. Regex pattern wrapped in single quotes
179
- - \`<path>\`: Required. Directory or file to search (use \`.\` for repo root)
210
+ ### \`grep\`
211
+ Search for pattern matches across files. Returns matches with 1 line of context above and below.
212
+ - Match lines use \`:\` separator \u2192 \`filepath:linenum:content\`
213
+ - Context lines use \`-\` separator \u2192 \`filepath-linenum-content\`
214
+
215
+ Elements:
216
+ - \`<pattern>\` (required): Search pattern (regex). Use \`(a|b)\` for OR patterns.
217
+ - \`<sub_dir>\` (optional): Subdirectory to search in (defaults to \`.\`)
218
+ - \`<glob>\` (optional): File pattern filter like \`*.py\` or \`*.{ts,tsx}\`
180
219
 
181
220
  Examples:
182
221
  \`\`\`
183
- grep 'class.*Service' src/
184
- grep 'def authenticate' .
185
- grep 'import.*from' src/components/
186
- grep 'TODO' .
222
+ <grep>
223
+ <pattern>(authenticate|authorize|login)</pattern>
224
+ <sub_dir>src/auth/</sub_dir>
225
+ </grep>
226
+
227
+ <grep>
228
+ <pattern>class.*(Service|Controller)</pattern>
229
+ <glob>*.{ts,js}</glob>
230
+ </grep>
231
+
232
+ <grep>
233
+ <pattern>(DB_HOST|DATABASE_URL|connection)</pattern>
234
+ <glob>*.{py,yaml,env}</glob>
235
+ <sub_dir>lib/</sub_dir>
236
+ </grep>
187
237
  \`\`\`
188
238
 
189
- ### \`finish <file1:ranges> [file2:ranges ...]\`
190
- Submit final answer with all relevant code locations.
191
- - Include generous line ranges\u2014don't be stingy with context
192
- - Ranges are comma-separated: \`file.py:10-30,50-60\`
193
- - ALWAYS include import statements at the top of files (usually lines 1-20)
194
- - If code spans multiple files, include ALL of them
195
- - Small files can be returned in full
239
+ ### \`finish\`
240
+ Submit final answer with all relevant code locations. Uses nested \`<file>\` elements.
241
+
242
+ File elements:
243
+ - \`<path>\` (required): File path
244
+ - \`<lines>\` (optional): Line ranges like "1-50,75-80" (\`*\` for entire file)
245
+
246
+ ALWAYS include import statements (usually lines 1-20). Better to over-include than miss context.
196
247
 
197
248
  Examples:
198
249
  \`\`\`
199
- finish src/auth.py:1-15,25-50,75-80 src/models/user.py:1-10,20-45
200
- finish src/index.ts:1-100
250
+ <finish>
251
+ <file>
252
+ <path>src/auth.py</path>
253
+ <lines>1-15,25-50,75-80</lines>
254
+ </file>
255
+ <file>
256
+ <path>src/models/user.py</path>
257
+ <lines>*</lines>
258
+ </file>
259
+ </finish>
201
260
  \`\`\`
202
261
  </tools>
203
262
 
204
263
  <strategy>
205
- **Before your first tool call, classify the query:**
264
+ **Before your first tool call, classify the search_string:**
206
265
 
207
- | Query Type | Turn 1 Strategy | Early Finish? |
208
- |------------|-----------------|---------------|
209
- | **Specific** (function name, error string, unique identifier) | 8 parallel greps on likely paths | Often by turn 2 |
210
- | **Conceptual** (how does X work, where is Y handled) | analyse + 2-3 broad greps | Rarely early |
211
- | **Exploratory** (find all tests, list API endpoints) | analyse at multiple depths | Usually needs 3 turns |
266
+ | Search_string Type | Round 1 Strategy | Early Finish? |
267
+ |------------|------------------|---------------|
268
+ | **Specific** (function name, error string, unique identifier) | 8 parallel greps on likely paths | Often by round 2 |
269
+ | **Conceptual** (how does X work, where is Y handled) | list_directory + 2-3 broad greps | Rarely early |
270
+ | **Exploratory** (find all tests, list API endpoints) | list_directory at multiple depths | Usually needs 3 rounds |
212
271
 
213
272
  **Parallel call patterns:**
214
273
  - **Shotgun grep**: Same pattern, 8 different directories\u2014fast coverage
215
274
  - **Variant grep**: 8 pattern variations (synonyms, naming conventions)\u2014catches inconsistent codebases
216
- - **Funnel**: 1 analyse + 7 greps\u2014orient and search simultaneously
275
+ - **Funnel**: 1 list_directory + 7 greps\u2014orient and search simultaneously
217
276
  - **Deep read**: 8 reads on files you already identified\u2014gather full context fast
277
+
278
+ **Tool call expectations:**
279
+ - 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.
280
+ - 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.
281
+ - 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.
282
+ - 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.
218
283
  </strategy>
219
284
 
220
285
  <output_format>
221
286
  EVERY response MUST follow this exact format:
222
287
 
223
288
  1. First, wrap your reasoning in \`<think>...</think>\` tags containing:
224
- - Query classification (specific/conceptual/exploratory)
225
- - Confidence estimate (can I finish in 1-2 turns?)
226
- - This turn's parallel strategy
289
+ - Search_string classification (specific/conceptual/exploratory)
290
+ - Confidence estimate (can I finish in 1-2 rounds?)
291
+ - This round's parallel strategy
227
292
  - What signals would let me finish early?
228
293
 
229
- 2. Then, output tool calls wrapped in \`<tool_call>...</tool_call>\` tags, one per line.
294
+ 2. Then, output up to 8 tool calls using nested XML elements.
230
295
 
231
296
  Example:
232
297
  \`\`\`
233
298
  <think>
234
- This is a specific query about authentication. I'll grep for auth-related patterns.
235
- High confidence I can finish in 2 turns if I find the auth module.
299
+ This is a specific search_string about authentication. I'll grep for auth-related patterns.
300
+ 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
236
301
  Strategy: Shotgun grep across likely directories.
237
302
  </think>
238
- <tool_call>grep 'authenticate' src/</tool_call>
239
- <tool_call>grep 'login' src/</tool_call>
240
- <tool_call>analyse src/auth</tool_call>
303
+ <grep>
304
+ <pattern>(authenticate|login|session)</pattern>
305
+ <sub_dir>src/auth/</sub_dir>
306
+ </grep>
307
+ <grep>
308
+ <pattern>(middleware|interceptor)</pattern>
309
+ <glob>*.{ts,js}</glob>
310
+ </grep>
311
+ <list_directory>
312
+ <path>src/auth</path>
313
+ </list_directory>
314
+ \`\`\`
315
+
316
+ Finishing example:
317
+ \`\`\`
318
+ <think>
319
+ I think I have a rough idea, but this is my last turn so I must call the finish tool regardless.
320
+ </think>
321
+ <finish>
322
+ <file>
323
+ <path>src/auth/login.py</path>
324
+ <lines>1-50</lines>
325
+ </file>
326
+ <file>
327
+ <path>src/middleware/session.py</path>
328
+ <lines>10-80</lines>
329
+ </file>
330
+ </finish>
241
331
  \`\`\`
242
332
 
243
333
  No commentary outside \`<think>\`. No explanations after tool calls.
@@ -250,17 +340,111 @@ When calling \`finish\`:
250
340
  - Include any type definitions, interfaces, or constants used
251
341
  - Better to over-include than leave the user missing context
252
342
  - If unsure about boundaries, include more rather than less
253
- </finishing_requirements>
254
-
255
- Begin your exploration now to find code relevant to the query.`;
343
+ </finishing_requirements>`;
256
344
  function getSystemPrompt() {
257
345
  return SYSTEM_PROMPT;
258
346
  }
259
347
 
260
348
  // tools/warp_grep/agent/parser.ts
261
- var VALID_COMMANDS = ["analyse", "grep", "read", "finish"];
349
+ var VALID_COMMANDS = ["list_directory", "grep", "read", "finish"];
350
+ function isValidCommand(name) {
351
+ return VALID_COMMANDS.includes(name);
352
+ }
353
+ function getXmlElementText(xml, tagName) {
354
+ const regex = new RegExp(`<${tagName}>([\\s\\S]*?)</${tagName}>`, "i");
355
+ const match = xml.match(regex);
356
+ return match ? match[1].trim() : null;
357
+ }
358
+ function parseNestedXmlTools(text) {
359
+ const tools = [];
360
+ const toolRegex = /<([a-z_][a-z0-9_]*)>([\s\S]*?)<\/\1>/gi;
361
+ let match;
362
+ while ((match = toolRegex.exec(text)) !== null) {
363
+ const rawToolName = match[1].toLowerCase();
364
+ const content = match[2];
365
+ if (!isValidCommand(rawToolName)) continue;
366
+ const toolName = rawToolName;
367
+ if (toolName === "list_directory") {
368
+ const path5 = getXmlElementText(content, "path");
369
+ const pattern = getXmlElementText(content, "pattern");
370
+ if (path5) {
371
+ tools.push({ name: "list_directory", arguments: { path: path5, pattern } });
372
+ }
373
+ } else if (toolName === "grep") {
374
+ const pattern = getXmlElementText(content, "pattern");
375
+ const subDir = getXmlElementText(content, "sub_dir");
376
+ const glob = getXmlElementText(content, "glob");
377
+ if (pattern) {
378
+ tools.push({
379
+ name: "grep",
380
+ arguments: {
381
+ pattern,
382
+ path: subDir || ".",
383
+ ...glob && { glob }
384
+ }
385
+ });
386
+ }
387
+ } else if (toolName === "read") {
388
+ const path5 = getXmlElementText(content, "path");
389
+ const linesStr = getXmlElementText(content, "lines");
390
+ if (path5) {
391
+ const args = { path: path5 };
392
+ if (linesStr) {
393
+ const ranges = [];
394
+ for (const rangeStr of linesStr.split(",")) {
395
+ const trimmed = rangeStr.trim();
396
+ if (!trimmed) continue;
397
+ const [s, e] = trimmed.split("-").map((v) => parseInt(v.trim(), 10));
398
+ if (Number.isFinite(s) && Number.isFinite(e)) {
399
+ ranges.push([s, e]);
400
+ } else if (Number.isFinite(s)) {
401
+ ranges.push([s, s]);
402
+ }
403
+ }
404
+ if (ranges.length === 1) {
405
+ args.start = ranges[0][0];
406
+ args.end = ranges[0][1];
407
+ } else if (ranges.length > 1) {
408
+ args.lines = ranges;
409
+ }
410
+ }
411
+ tools.push({ name: "read", arguments: args });
412
+ }
413
+ } else if (toolName === "finish") {
414
+ const fileRegex = /<file>([\s\S]*?)<\/file>/gi;
415
+ const files = [];
416
+ let fileMatch;
417
+ while ((fileMatch = fileRegex.exec(content)) !== null) {
418
+ const fileContent = fileMatch[1];
419
+ const filePath = getXmlElementText(fileContent, "path");
420
+ const linesStr = getXmlElementText(fileContent, "lines");
421
+ if (filePath && linesStr) {
422
+ const ranges = [];
423
+ for (const rangeStr of linesStr.split(",")) {
424
+ if (rangeStr.trim() === "*") {
425
+ ranges.push([1, 999999]);
426
+ } else {
427
+ const [s, e] = rangeStr.split("-").map((v) => parseInt(v.trim(), 10));
428
+ if (Number.isFinite(s) && Number.isFinite(e)) {
429
+ ranges.push([s, e]);
430
+ }
431
+ }
432
+ }
433
+ if (ranges.length > 0) {
434
+ files.push({ path: filePath, lines: ranges });
435
+ }
436
+ }
437
+ }
438
+ if (files.length > 0) {
439
+ tools.push({ name: "finish", arguments: { files } });
440
+ }
441
+ }
442
+ }
443
+ return tools;
444
+ }
262
445
  function preprocessText(text) {
263
446
  let processed = text.replace(/<think>[\s\S]*?<\/think>/gi, "");
447
+ const nestedTools = parseNestedXmlTools(processed);
264
448
  const openingTagRegex = /<tool_call>|<tool>/gi;
265
449
  const closingTagRegex = /<\/tool_call>|<\/tool>/gi;
266
450
  const openingMatches = processed.match(openingTagRegex) || [];
@@ -297,7 +481,7 @@ function preprocessText(text) {
297
481
  }
298
482
  }
299
483
  }
300
- return toolCallLines;
484
+ return { lines: toolCallLines, nestedTools };
301
485
  }
302
486
  var LLMResponseParser = class {
303
487
  finishSpecSplitRe = /,(?=[^,\s]+:)/;
@@ -305,8 +489,8 @@ var LLMResponseParser = class {
305
489
  if (typeof text !== "string") {
306
490
  throw new TypeError("Command text must be a string.");
307
491
  }
308
- const lines = preprocessText(text);
309
- const commands = [];
492
+ const { lines, nestedTools } = preprocessText(text);
493
+ const commands = [...nestedTools];
310
494
  let finishAccumulator = null;
311
495
  lines.forEach((line) => {
312
496
  if (!line || line.startsWith("#")) return;
@@ -314,8 +498,8 @@ var LLMResponseParser = class {
314
498
  if (parts.length === 0) return;
315
499
  const cmd = parts[0];
316
500
  switch (cmd) {
317
- case "analyse":
318
- this.handleAnalyse(parts, line, commands);
501
+ case "list_directory":
502
+ this.handleListDirectory(parts, line, commands);
319
503
  break;
320
504
  case "grep":
321
505
  this.handleGrep(parts, line, commands);
@@ -333,8 +517,8 @@ var LLMResponseParser = class {
333
517
  if (finishAccumulator) {
334
518
  const map = finishAccumulator;
335
519
  const entries = [...map.entries()];
336
- const filesPayload = entries.map(([path4, ranges]) => ({
337
- path: path4,
520
+ const filesPayload = entries.map(([path5, ranges]) => ({
521
+ path: path5,
338
522
  lines: [...ranges].sort((a, b) => a[0] - b[0])
339
523
  }));
340
524
  commands.push({ name: "finish", arguments: { files: filesPayload } });
@@ -366,18 +550,17 @@ var LLMResponseParser = class {
366
550
  skip(message) {
367
551
  return { name: "_skip", arguments: { message } };
368
552
  }
369
- handleAnalyse(parts, rawLine, commands) {
553
+ handleListDirectory(parts, rawLine, commands) {
370
554
  if (parts.length < 2) {
371
555
  commands.push(this.skip(
372
- `[SKIPPED] Your command "${rawLine}" is missing a path. Correct format: analyse <path> [pattern]. Example: analyse src/`
556
+ `[SKIPPED] Your command "${rawLine}" is missing a path. Correct format: list_directory <path> [pattern]. Example: list_directory src/`
373
557
  ));
374
558
  return;
375
559
  }
376
- const path4 = parts[1];
560
+ const path5 = parts[1];
377
561
  const pattern = parts[2]?.replace(/^"|"$/g, "") ?? null;
378
- commands.push({ name: "analyse", arguments: { path: path4, pattern } });
562
+ commands.push({ name: "list_directory", arguments: { path: path5, pattern } });
379
563
  }
380
- // no glob tool in MCP
381
564
  handleGrep(parts, rawLine, commands) {
382
565
  if (parts.length < 3) {
383
566
  commands.push(this.skip(
@@ -448,8 +631,30 @@ var LLMResponseParser = class {
448
631
  }
449
632
  };
450
633
 
451
- // tools/warp_grep/tools/read.ts
634
+ // tools/warp_grep/agent/tools/grep.ts
635
+ async function toolGrep(provider, args) {
636
+ const res = await provider.grep(args);
637
+ if (res.error) {
638
+ return { output: res.error };
639
+ }
640
+ if (!res.lines.length) {
641
+ return { output: "no matches" };
642
+ }
643
+ return { output: res.lines.join("\n") };
644
+ }
645
+
646
+ // tools/warp_grep/agent/tools/read.ts
452
647
  async function toolRead(provider, args) {
648
+ if (args.lines && args.lines.length > 0) {
649
+ const chunks = [];
650
+ for (const [start, end] of args.lines) {
651
+ const res2 = await provider.read({ path: args.path, start, end });
652
+ if (res2.error) return res2.error;
653
+ chunks.push(res2.lines.join("\n"));
654
+ }
655
+ if (chunks.every((c) => c === "")) return "(empty file)";
656
+ return chunks.join("\n...\n");
657
+ }
453
658
  const res = await provider.read({ path: args.path, start: args.start, end: args.end });
454
659
  if (res.error) {
455
660
  return res.error;
@@ -458,16 +663,62 @@ async function toolRead(provider, args) {
458
663
  return res.lines.join("\n");
459
664
  }
460
665
 
461
- // tools/warp_grep/tools/analyse.ts
462
- async function toolAnalyse(provider, args) {
463
- const list = await provider.analyse({
666
+ // tools/warp_grep/agent/tools/list_directory.ts
667
+ async function toolListDirectory(provider, args) {
668
+ const list = await provider.listDirectory({
464
669
  path: args.path,
465
670
  pattern: args.pattern ?? null,
466
- maxResults: args.maxResults ?? 100,
467
- maxDepth: args.maxDepth ?? 2
671
+ maxResults: args.maxResults ?? AGENT_CONFIG.MAX_OUTPUT_LINES,
672
+ maxDepth: args.maxDepth ?? AGENT_CONFIG.MAX_LIST_DEPTH
468
673
  });
469
674
  if (!list.length) return "empty";
470
- return list.map((e) => `${" ".repeat(e.depth)}- ${e.type === "dir" ? "[D]" : "[F]"} ${e.name}`).join("\n");
675
+ if (list.length >= AGENT_CONFIG.MAX_OUTPUT_LINES) {
676
+ return "query not specific enough, tool called tried to return too much context and failed";
677
+ }
678
+ return list.map((e) => {
679
+ const indent = " ".repeat(e.depth);
680
+ const name = e.type === "dir" ? `${e.name}/` : e.name;
681
+ return `${indent}${name}`;
682
+ }).join("\n");
683
+ }
684
+
685
+ // tools/warp_grep/agent/tools/finish.ts
686
+ function normalizeFinishFiles(files) {
687
+ return files.map((f) => {
688
+ const merged = mergeRanges(f.lines);
689
+ return { path: f.path, lines: merged };
690
+ });
691
+ }
692
+ async function readFinishFiles(repoRoot, files, reader) {
693
+ const out = [];
694
+ for (const f of files) {
695
+ const ranges = mergeRanges(f.lines);
696
+ const chunks = [];
697
+ for (const [s, e] of ranges) {
698
+ const lines = await reader(f.path, s, e);
699
+ chunks.push(lines.join("\n"));
700
+ }
701
+ out.push({ path: f.path, ranges, content: chunks.join("\n") });
702
+ }
703
+ return out;
704
+ }
705
+ function mergeRanges(ranges) {
706
+ if (!ranges.length) return [];
707
+ const sorted = [...ranges].sort((a, b) => a[0] - b[0]);
708
+ const merged = [];
709
+ let [cs, ce] = sorted[0];
710
+ for (let i = 1; i < sorted.length; i++) {
711
+ const [s, e] = sorted[i];
712
+ if (s <= ce + 1) {
713
+ ce = Math.max(ce, e);
714
+ } else {
715
+ merged.push([cs, ce]);
716
+ cs = s;
717
+ ce = e;
718
+ }
719
+ }
720
+ merged.push([cs, ce]);
721
+ return merged;
471
722
  }
472
723
 
473
724
  // tools/utils/resilience.ts
@@ -560,8 +811,8 @@ var ToolOutputFormatter = class {
560
811
  switch (name) {
561
812
  case "read":
562
813
  return this.formatRead(safeArgs, payload, isError);
563
- case "analyse":
564
- return this.formatAnalyse(safeArgs, payload, isError);
814
+ case "list_directory":
815
+ return this.formatListDirectory(safeArgs, payload, isError);
565
816
  case "grep":
566
817
  return this.formatGrep(safeArgs, payload, isError);
567
818
  default:
@@ -574,39 +825,56 @@ ${payload}
574
825
  if (isError) {
575
826
  return payload;
576
827
  }
577
- const path4 = this.asString(args.path) || "...";
578
- return `<file path="${path4}">
828
+ const path5 = this.asString(args.path) || "...";
829
+ const start = args.start;
830
+ const end = args.end;
831
+ const linesArray = args.lines;
832
+ const attributes = [`path="${path5}"`];
833
+ if (linesArray && linesArray.length > 0) {
834
+ const rangeStr = linesArray.map(([s, e]) => `${s}-${e}`).join(",");
835
+ attributes.push(`lines="${rangeStr}"`);
836
+ } else if (start !== void 0 && end !== void 0) {
837
+ attributes.push(`lines="${start}-${end}"`);
838
+ }
839
+ return `<read ${attributes.join(" ")}>
579
840
  ${payload}
580
- </file>`;
841
+ </read>`;
581
842
  }
582
- formatAnalyse(args, payload, isError) {
583
- const path4 = this.asString(args.path) || ".";
843
+ formatListDirectory(args, payload, isError) {
844
+ const path5 = this.asString(args.path) || ".";
845
+ const pattern = this.asString(args.pattern);
846
+ const attributes = [`path="${path5}"`];
847
+ if (pattern) {
848
+ attributes.push(`pattern="${pattern}"`);
849
+ }
584
850
  if (isError) {
585
- return `<analyse_results path="${path4}" status="error">
586
- ${payload}
587
- </analyse_results>`;
851
+ attributes.push('status="error"');
588
852
  }
589
- return `<analyse_results path="${path4}">
853
+ return `<list_directory ${attributes.join(" ")}>
590
854
  ${payload}
591
- </analyse_results>`;
855
+ </list_directory>`;
592
856
  }
593
857
  formatGrep(args, payload, isError) {
594
858
  const pattern = this.asString(args.pattern);
595
- const path4 = this.asString(args.path);
859
+ const subDir = this.asString(args.path);
860
+ const glob = this.asString(args.glob);
596
861
  const attributes = [];
597
862
  if (pattern !== void 0) {
598
863
  attributes.push(`pattern="${pattern}"`);
599
864
  }
600
- if (path4 !== void 0) {
601
- attributes.push(`path="${path4}"`);
865
+ if (subDir !== void 0) {
866
+ attributes.push(`sub_dir="${subDir}"`);
867
+ }
868
+ if (glob !== void 0) {
869
+ attributes.push(`glob="${glob}"`);
602
870
  }
603
871
  if (isError) {
604
872
  attributes.push('status="error"');
605
873
  }
606
874
  const attrText = attributes.length ? ` ${attributes.join(" ")}` : "";
607
- return `<grep_output${attrText}>
875
+ return `<grep${attrText}>
608
876
  ${payload}
609
- </grep_output>`;
877
+ </grep>`;
610
878
  }
611
879
  asString(value) {
612
880
  if (value === null || value === void 0) {
@@ -620,180 +888,100 @@ function formatAgentToolOutput(toolName, args, output, options = {}) {
620
888
  return sharedFormatter.format(toolName, args, output, options);
621
889
  }
622
890
 
623
- // tools/warp_grep/agent/grep_helpers.ts
624
- var GrepState = class {
625
- seenLines = /* @__PURE__ */ new Set();
626
- isNew(path4, lineNumber) {
627
- const key = this.makeKey(path4, lineNumber);
628
- return !this.seenLines.has(key);
629
- }
630
- add(path4, lineNumber) {
631
- this.seenLines.add(this.makeKey(path4, lineNumber));
632
- }
633
- makeKey(path4, lineNumber) {
634
- return `${path4}:${lineNumber}`;
635
- }
636
- };
637
- var MAX_GREP_OUTPUT_CHARS_PER_TURN = 6e4;
638
- function extractMatchFields(payload) {
639
- const text = payload.replace(/\r?\n$/, "");
640
- if (!text || text.startsWith("[error]")) {
641
- return null;
642
- }
643
- const firstSep = text.indexOf(":");
644
- if (firstSep === -1) {
645
- return null;
646
- }
647
- let filePath = text.slice(0, firstSep).trim();
648
- if (!filePath) {
649
- return null;
650
- }
651
- if (filePath.startsWith("./") || filePath.startsWith(".\\")) {
652
- filePath = filePath.slice(2);
653
- }
654
- const remainder = text.slice(firstSep + 1);
655
- const secondSep = remainder.indexOf(":");
656
- if (secondSep === -1) {
657
- return null;
658
- }
659
- const linePart = remainder.slice(0, secondSep);
660
- const lineNumber = Number.parseInt(linePart, 10);
661
- if (!Number.isInteger(lineNumber) || lineNumber <= 0) {
662
- return null;
663
- }
664
- let contentSegment = remainder.slice(secondSep + 1);
665
- const columnSep = contentSegment.indexOf(":");
666
- if (columnSep !== -1 && /^\d+$/.test(contentSegment.slice(0, columnSep))) {
667
- contentSegment = contentSegment.slice(columnSep + 1);
668
- }
669
- const content = contentSegment.trim();
670
- if (!content) {
671
- return null;
891
+ // tools/warp_grep/agent/helpers.ts
892
+ var import_path = __toESM(require("path"), 1);
893
+ var TRUNCATED_MARKER = "[truncated for context limit]";
894
+ function formatTurnMessage(turnsUsed, maxTurns) {
895
+ const turnsRemaining = maxTurns - turnsUsed;
896
+ if (turnsRemaining === 1) {
897
+ return `
898
+ 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`;
672
899
  }
673
- return { path: filePath, lineNumber, content };
900
+ return `
901
+ You have used ${turnsUsed} turn${turnsUsed === 1 ? "" : "s"} and have ${turnsRemaining} remaining`;
674
902
  }
675
- function parseAndFilterGrepOutput(rawOutput, state) {
676
- const matches = [];
677
- if (typeof rawOutput !== "string" || !rawOutput.trim()) {
678
- return matches;
679
- }
680
- for (const line of rawOutput.split(/\r?\n/)) {
681
- const fields = extractMatchFields(line);
682
- if (!fields) {
683
- continue;
684
- }
685
- if (state.isNew(fields.path, fields.lineNumber)) {
686
- matches.push(fields);
687
- state.add(fields.path, fields.lineNumber);
688
- }
689
- }
690
- return matches;
903
+ function calculateContextBudget(messages) {
904
+ const totalChars = messages.reduce((sum, m) => sum + m.content.length, 0);
905
+ const maxChars = AGENT_CONFIG.MAX_CONTEXT_CHARS;
906
+ const percent = Math.round(totalChars / maxChars * 100);
907
+ const usedK = Math.round(totalChars / 1e3);
908
+ const maxK = Math.round(maxChars / 1e3);
909
+ return `<context_budget>${percent}% (${usedK}K/${maxK}K chars)</context_budget>`;
691
910
  }
692
- function truncateOutput(payload, maxChars) {
693
- if (payload.length <= maxChars) {
694
- return payload;
695
- }
696
- const note = "... (output truncated)";
697
- const available = maxChars - note.length - 1;
698
- if (available <= 0) {
699
- return note;
700
- }
701
- if (payload.length <= available) {
702
- return `${payload.slice(0, available).replace(/\n$/, "")}
703
- ${note}`;
911
+ async function buildInitialState(repoRoot, query, provider) {
912
+ try {
913
+ const entries = await provider.listDirectory({
914
+ path: ".",
915
+ maxResults: AGENT_CONFIG.MAX_OUTPUT_LINES,
916
+ maxDepth: 2
917
+ });
918
+ const treeLines = entries.map((e) => {
919
+ const indent = " ".repeat(e.depth);
920
+ const name = e.type === "dir" ? `${e.name}/` : e.name;
921
+ return `${indent}${name}`;
922
+ });
923
+ const repoName = import_path.default.basename(repoRoot);
924
+ const treeOutput = treeLines.length > 0 ? `${repoName}/
925
+ ${treeLines.join("\n")}` : `${repoName}/`;
926
+ return `<repo_structure>
927
+ ${treeOutput}
928
+ </repo_structure>
929
+
930
+ <search_string>
931
+ ${query}
932
+ </search_string>`;
933
+ } catch {
934
+ const repoName = import_path.default.basename(repoRoot);
935
+ return `<repo_structure>
936
+ ${repoName}/
937
+ </repo_structure>
938
+
939
+ <search_string>
940
+ ${query}
941
+ </search_string>`;
704
942
  }
705
- const core = payload.slice(0, Math.max(0, available - 1));
706
- const trimmed = core.replace(/\n$/, "").replace(/\s+$/, "");
707
- const snippet = trimmed ? `${trimmed}\u2026` : "\u2026";
708
- return `${snippet}
709
- ${note}`;
710
943
  }
711
- function formatTurnGrepOutput(matches, maxChars = MAX_GREP_OUTPUT_CHARS_PER_TURN) {
712
- if (!matches || matches.length === 0) {
713
- return "No new matches found.";
944
+ function enforceContextLimit(messages, maxChars = AGENT_CONFIG.MAX_CONTEXT_CHARS) {
945
+ const getTotalChars = () => messages.reduce((sum, m) => sum + m.content.length, 0);
946
+ if (getTotalChars() <= maxChars) {
947
+ return messages;
714
948
  }
715
- const matchesByFile = /* @__PURE__ */ new Map();
716
- for (const match of matches) {
717
- if (!matchesByFile.has(match.path)) {
718
- matchesByFile.set(match.path, []);
949
+ const userIndices = [];
950
+ let firstUserSkipped = false;
951
+ for (let i = 0; i < messages.length; i++) {
952
+ if (messages[i].role === "user") {
953
+ if (!firstUserSkipped) {
954
+ firstUserSkipped = true;
955
+ continue;
956
+ }
957
+ userIndices.push(i);
719
958
  }
720
- matchesByFile.get(match.path).push(match);
721
959
  }
722
- const lines = [];
723
- const sortedPaths = Array.from(matchesByFile.keys()).sort();
724
- sortedPaths.forEach((filePath, index) => {
725
- if (index > 0) {
726
- lines.push("");
727
- }
728
- lines.push(filePath);
729
- const sortedMatches = matchesByFile.get(filePath).slice().sort((a, b) => a.lineNumber - b.lineNumber);
730
- for (const match of sortedMatches) {
731
- lines.push(`${match.lineNumber}:${match.content}`);
960
+ for (const idx of userIndices) {
961
+ if (getTotalChars() <= maxChars) {
962
+ break;
732
963
  }
733
- });
734
- return truncateOutput(lines.join("\n"), maxChars);
735
- }
736
-
737
- // tools/warp_grep/tools/finish.ts
738
- async function readFinishFiles(repoRoot, files, reader) {
739
- const out = [];
740
- for (const f of files) {
741
- const ranges = mergeRanges(f.lines);
742
- const chunks = [];
743
- for (const [s, e] of ranges) {
744
- const lines = await reader(f.path, s, e);
745
- chunks.push(lines.join("\n"));
964
+ if (messages[idx].content !== TRUNCATED_MARKER) {
965
+ messages[idx] = { role: "user", content: TRUNCATED_MARKER };
746
966
  }
747
- out.push({ path: f.path, ranges, content: chunks.join("\n") });
748
967
  }
749
- return out;
750
- }
751
- function mergeRanges(ranges) {
752
- if (!ranges.length) return [];
753
- const sorted = [...ranges].sort((a, b) => a[0] - b[0]);
754
- const merged = [];
755
- let [cs, ce] = sorted[0];
756
- for (let i = 1; i < sorted.length; i++) {
757
- const [s, e] = sorted[i];
758
- if (s <= ce + 1) {
759
- ce = Math.max(ce, e);
760
- } else {
761
- merged.push([cs, ce]);
762
- cs = s;
763
- ce = e;
764
- }
765
- }
766
- merged.push([cs, ce]);
767
- return merged;
968
+ return messages;
768
969
  }
769
970
 
770
971
  // tools/warp_grep/agent/runner.ts
771
- var import_path = __toESM(require("path"), 1);
972
+ var import_path2 = __toESM(require("path"), 1);
772
973
  var parser = new LLMResponseParser();
773
- async function buildInitialState(repoRoot, query, provider) {
774
- try {
775
- const entries = await provider.analyse({ path: ".", maxResults: 100 });
776
- const dirs = entries.filter((e) => e.type === "dir").map((d) => d.name).slice(0, 50);
777
- const files = entries.filter((e) => e.type === "file").map((f) => f.name).slice(0, 50);
778
- const parts = [
779
- `<repo_root>${repoRoot}</repo_root>`,
780
- `<top_dirs>${dirs.join(", ")}</top_dirs>`,
781
- `<top_files>${files.join(", ")}</top_files>`
782
- ];
783
- return parts.join("\n");
784
- } catch {
785
- return `<repo_root>${repoRoot}</repo_root>`;
786
- }
787
- }
788
- async function callModel(messages, model, apiKey) {
789
- const api = "https://api.morphllm.com/v1/chat/completions";
974
+ var DEFAULT_API_URL = "https://api.morphllm.com";
975
+ async function callModel(messages, model, options = {}) {
976
+ const baseUrl = DEFAULT_API_URL;
977
+ const apiKey = options.morphApiKey || process.env.MORPH_API_KEY || "";
790
978
  const fetchPromise = fetchWithRetry(
791
- api,
979
+ `${baseUrl}/v1/chat/completions`,
792
980
  {
793
981
  method: "POST",
794
982
  headers: {
795
983
  "Content-Type": "application/json",
796
- Authorization: `Bearer ${apiKey || process.env.MORPH_API_KEY || ""}`
984
+ Authorization: `Bearer ${apiKey}`
797
985
  },
798
986
  body: JSON.stringify({
799
987
  model,
@@ -802,10 +990,15 @@ async function callModel(messages, model, apiKey) {
802
990
  messages
803
991
  })
804
992
  },
805
- {}
993
+ options.retryConfig
806
994
  );
807
995
  const resp = await withTimeout(fetchPromise, AGENT_CONFIG.TIMEOUT_MS, "morph-warp-grep request timed out");
808
996
  if (!resp.ok) {
997
+ if (resp.status === 404) {
998
+ throw new Error(
999
+ "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"
1000
+ );
1001
+ }
809
1002
  const t = await resp.text();
810
1003
  throw new Error(`morph-warp-grep error ${resp.status}: ${t}`);
811
1004
  }
@@ -817,23 +1010,24 @@ async function callModel(messages, model, apiKey) {
817
1010
  return content;
818
1011
  }
819
1012
  async function runWarpGrep(config) {
820
- const repoRoot = import_path.default.resolve(config.repoRoot || process.cwd());
1013
+ const repoRoot = import_path2.default.resolve(config.repoRoot || process.cwd());
821
1014
  const messages = [];
822
- const systemMessage = { role: "system", content: getSystemPrompt() };
823
- messages.push(systemMessage);
824
- const queryContent = `<query>${config.query}</query>`;
825
- messages.push({ role: "user", content: queryContent });
1015
+ messages.push({ role: "system", content: getSystemPrompt() });
826
1016
  const initialState = await buildInitialState(repoRoot, config.query, config.provider);
827
1017
  messages.push({ role: "user", content: initialState });
828
- const maxRounds = AGENT_CONFIG.MAX_ROUNDS;
1018
+ const maxTurns = AGENT_CONFIG.MAX_TURNS;
829
1019
  const model = config.model || DEFAULT_MODEL;
830
1020
  const provider = config.provider;
831
1021
  const errors = [];
832
- const grepState = new GrepState();
833
1022
  let finishMeta;
834
1023
  let terminationReason = "terminated";
835
- for (let round = 1; round <= maxRounds; round += 1) {
836
- const assistantContent = await callModel(messages, model, config.apiKey).catch((e) => {
1024
+ for (let turn = 1; turn <= maxTurns; turn += 1) {
1025
+ enforceContextLimit(messages);
1026
+ const assistantContent = await callModel(messages, model, {
1027
+ morphApiKey: config.morphApiKey,
1028
+ morphApiUrl: config.morphApiUrl,
1029
+ retryConfig: config.retryConfig
1030
+ }).catch((e) => {
837
1031
  errors.push({ message: e instanceof Error ? e.message : String(e) });
838
1032
  return "";
839
1033
  });
@@ -841,13 +1035,13 @@ async function runWarpGrep(config) {
841
1035
  messages.push({ role: "assistant", content: assistantContent });
842
1036
  const toolCalls = parser.parse(assistantContent);
843
1037
  if (toolCalls.length === 0) {
844
- errors.push({ message: "No tool calls produced by the model." });
1038
+ 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" });
845
1039
  terminationReason = "terminated";
846
1040
  break;
847
1041
  }
848
1042
  const finishCalls = toolCalls.filter((c) => c.name === "finish");
849
1043
  const grepCalls = toolCalls.filter((c) => c.name === "grep");
850
- const analyseCalls = toolCalls.filter((c) => c.name === "analyse");
1044
+ const listDirCalls = toolCalls.filter((c) => c.name === "list_directory");
851
1045
  const readCalls = toolCalls.filter((c) => c.name === "read");
852
1046
  const skipCalls = toolCalls.filter((c) => c.name === "_skip");
853
1047
  const formatted = [];
@@ -855,69 +1049,42 @@ async function runWarpGrep(config) {
855
1049
  const msg = c.arguments?.message || "Command skipped due to parsing error";
856
1050
  formatted.push(msg);
857
1051
  }
858
- const otherPromises = [];
859
- for (const c of analyseCalls) {
1052
+ const allPromises = [];
1053
+ for (const c of grepCalls) {
1054
+ const args = c.arguments ?? {};
1055
+ allPromises.push(
1056
+ toolGrep(provider, args).then(
1057
+ ({ output }) => formatAgentToolOutput("grep", args, output, { isError: false }),
1058
+ (err) => formatAgentToolOutput("grep", args, String(err), { isError: true })
1059
+ )
1060
+ );
1061
+ }
1062
+ for (const c of listDirCalls) {
860
1063
  const args = c.arguments ?? {};
861
- otherPromises.push(
862
- toolAnalyse(provider, args).then(
863
- (p) => formatAgentToolOutput("analyse", args, p, { isError: false }),
864
- (err) => formatAgentToolOutput("analyse", args, String(err), { isError: true })
1064
+ allPromises.push(
1065
+ toolListDirectory(provider, args).then(
1066
+ (p) => formatAgentToolOutput("list_directory", args, p, { isError: false }),
1067
+ (err) => formatAgentToolOutput("list_directory", args, String(err), { isError: true })
865
1068
  )
866
1069
  );
867
1070
  }
868
1071
  for (const c of readCalls) {
869
1072
  const args = c.arguments ?? {};
870
- otherPromises.push(
1073
+ allPromises.push(
871
1074
  toolRead(provider, args).then(
872
1075
  (p) => formatAgentToolOutput("read", args, p, { isError: false }),
873
1076
  (err) => formatAgentToolOutput("read", args, String(err), { isError: true })
874
1077
  )
875
1078
  );
876
1079
  }
877
- const otherResults = await Promise.all(otherPromises);
878
- formatted.push(...otherResults);
879
- for (const c of grepCalls) {
880
- const args = c.arguments ?? {};
881
- try {
882
- const grepRes = await provider.grep({ pattern: args.pattern, path: args.path });
883
- if (grepRes.error) {
884
- errors.push({ message: grepRes.error });
885
- terminationReason = "terminated";
886
- return {
887
- terminationReason: "terminated",
888
- messages,
889
- errors
890
- };
891
- }
892
- const rawOutput = Array.isArray(grepRes.lines) ? grepRes.lines.join("\n") : "";
893
- const newMatches = parseAndFilterGrepOutput(rawOutput, grepState);
894
- let formattedPayload = formatTurnGrepOutput(newMatches);
895
- if (formattedPayload === "No new matches found.") {
896
- formattedPayload = "no new matches";
897
- }
898
- formatted.push(formatAgentToolOutput("grep", args, formattedPayload, { isError: false }));
899
- } catch (err) {
900
- formatted.push(formatAgentToolOutput("grep", args, String(err), { isError: true }));
901
- }
1080
+ const allResults = await Promise.all(allPromises);
1081
+ for (const result of allResults) {
1082
+ formatted.push(result);
902
1083
  }
903
1084
  if (formatted.length > 0) {
904
- const turnsUsed = round;
905
- const turnsRemaining = 4 - turnsUsed;
906
- let turnMessage;
907
- if (turnsRemaining === 0) {
908
- turnMessage = `
909
-
910
- [Turn ${turnsUsed}/4] This is your LAST turn. You MUST call the finish tool now.`;
911
- } else if (turnsRemaining === 1) {
912
- turnMessage = `
913
-
914
- [Turn ${turnsUsed}/4] You have 1 turn remaining. Next turn you MUST call the finish tool.`;
915
- } else {
916
- turnMessage = `
917
-
918
- [Turn ${turnsUsed}/4] You have ${turnsRemaining} turns remaining.`;
919
- }
920
- messages.push({ role: "user", content: formatted.join("\n") + turnMessage });
1085
+ const turnMessage = formatTurnMessage(turn, maxTurns);
1086
+ const contextBudget = calculateContextBudget(messages);
1087
+ messages.push({ role: "user", content: formatted.join("\n") + turnMessage + "\n" + contextBudget });
921
1088
  }
922
1089
  if (finishCalls.length) {
923
1090
  const fc = finishCalls[0];
@@ -967,7 +1134,7 @@ async function runWarpGrep(config) {
967
1134
 
968
1135
  // tools/warp_grep/providers/local.ts
969
1136
  var import_promises2 = __toESM(require("fs/promises"), 1);
970
- var import_path3 = __toESM(require("path"), 1);
1137
+ var import_path4 = __toESM(require("path"), 1);
971
1138
 
972
1139
  // tools/warp_grep/utils/ripgrep.ts
973
1140
  var import_child_process = require("child_process");
@@ -1032,21 +1199,21 @@ async function runRipgrep(args, opts) {
1032
1199
 
1033
1200
  // tools/warp_grep/utils/paths.ts
1034
1201
  var import_fs = __toESM(require("fs"), 1);
1035
- var import_path2 = __toESM(require("path"), 1);
1202
+ var import_path3 = __toESM(require("path"), 1);
1036
1203
  function resolveUnderRepo(repoRoot, targetPath) {
1037
- const absRoot = import_path2.default.resolve(repoRoot);
1038
- const resolved = import_path2.default.resolve(absRoot, targetPath);
1204
+ const absRoot = import_path3.default.resolve(repoRoot);
1205
+ const resolved = import_path3.default.resolve(absRoot, targetPath);
1039
1206
  ensureWithinRepo(absRoot, resolved);
1040
1207
  return resolved;
1041
1208
  }
1042
1209
  function ensureWithinRepo(repoRoot, absTarget) {
1043
- const rel = import_path2.default.relative(import_path2.default.resolve(repoRoot), import_path2.default.resolve(absTarget));
1044
- if (rel.startsWith("..") || import_path2.default.isAbsolute(rel)) {
1210
+ const rel = import_path3.default.relative(import_path3.default.resolve(repoRoot), import_path3.default.resolve(absTarget));
1211
+ if (rel.startsWith("..") || import_path3.default.isAbsolute(rel)) {
1045
1212
  throw new Error(`Path outside repository root: ${absTarget}`);
1046
1213
  }
1047
1214
  }
1048
1215
  function toRepoRelative(repoRoot, absPath) {
1049
- return import_path2.default.relative(import_path2.default.resolve(repoRoot), import_path2.default.resolve(absPath));
1216
+ return import_path3.default.relative(import_path3.default.resolve(repoRoot), import_path3.default.resolve(absPath));
1050
1217
  }
1051
1218
  function isSymlink(p) {
1052
1219
  try {
@@ -1089,10 +1256,18 @@ var LocalRipgrepProvider = class {
1089
1256
  this.excludes = excludes;
1090
1257
  }
1091
1258
  async grep(params) {
1092
- const abs = resolveUnderRepo(this.repoRoot, params.path);
1259
+ let abs;
1260
+ try {
1261
+ abs = resolveUnderRepo(this.repoRoot, params.path);
1262
+ } catch (err) {
1263
+ return {
1264
+ lines: [],
1265
+ error: `[PATH ERROR] ${err instanceof Error ? err.message : String(err)}`
1266
+ };
1267
+ }
1093
1268
  const stat = await import_promises2.default.stat(abs).catch(() => null);
1094
1269
  if (!stat) return { lines: [] };
1095
- const targetArg = abs === import_path3.default.resolve(this.repoRoot) ? "." : toRepoRelative(this.repoRoot, abs);
1270
+ const targetArg = abs === import_path4.default.resolve(this.repoRoot) ? "." : toRepoRelative(this.repoRoot, abs);
1096
1271
  const args = [
1097
1272
  "--no-config",
1098
1273
  "--no-heading",
@@ -1101,6 +1276,9 @@ var LocalRipgrepProvider = class {
1101
1276
  "--color=never",
1102
1277
  "--trim",
1103
1278
  "--max-columns=400",
1279
+ "-C",
1280
+ "1",
1281
+ ...params.glob ? ["--glob", params.glob] : [],
1104
1282
  ...this.excludes.flatMap((e) => ["-g", `!${e}`]),
1105
1283
  params.pattern,
1106
1284
  targetArg || "."
@@ -1125,29 +1303,24 @@ Details: ${res.stderr}` : ""}`
1125
1303
  };
1126
1304
  }
1127
1305
  const lines = (res.stdout || "").trim().split(/\r?\n/).filter((l) => l.length > 0);
1128
- return { lines };
1129
- }
1130
- async glob(params) {
1131
- const abs = resolveUnderRepo(this.repoRoot, params.path);
1132
- const targetArg = abs === import_path3.default.resolve(this.repoRoot) ? "." : toRepoRelative(this.repoRoot, abs);
1133
- const args = [
1134
- "--no-config",
1135
- "--files",
1136
- "-g",
1137
- params.pattern,
1138
- ...this.excludes.flatMap((e) => ["-g", `!${e}`]),
1139
- targetArg || "."
1140
- ];
1141
- const res = await runRipgrep(args, { cwd: this.repoRoot });
1142
- if (res.exitCode === -1) {
1143
- console.warn(`[warp_grep] ripgrep not available for glob: ${res.stderr || "execution failed"}`);
1144
- return { files: [] };
1306
+ if (lines.length > AGENT_CONFIG.MAX_OUTPUT_LINES) {
1307
+ return {
1308
+ lines: [],
1309
+ error: "query not specific enough, tool tried to return too much context and failed"
1310
+ };
1145
1311
  }
1146
- const files = (res.stdout || "").trim().split(/\r?\n/).filter((l) => l.length > 0);
1147
- return { files };
1312
+ return { lines };
1148
1313
  }
1149
1314
  async read(params) {
1150
- const abs = resolveUnderRepo(this.repoRoot, params.path);
1315
+ let abs;
1316
+ try {
1317
+ abs = resolveUnderRepo(this.repoRoot, params.path);
1318
+ } catch (err) {
1319
+ return {
1320
+ lines: [],
1321
+ error: `[PATH ERROR] ${err instanceof Error ? err.message : String(err)}`
1322
+ };
1323
+ }
1151
1324
  const stat = await import_promises2.default.stat(abs).catch(() => null);
1152
1325
  if (!stat || !stat.isFile()) {
1153
1326
  return {
@@ -1167,7 +1340,15 @@ Details: ${res.stderr}` : ""}`
1167
1340
  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.`
1168
1341
  };
1169
1342
  }
1170
- const lines = await readAllLines(abs);
1343
+ let lines;
1344
+ try {
1345
+ lines = await readAllLines(abs);
1346
+ } catch (err) {
1347
+ return {
1348
+ lines: [],
1349
+ error: `[READ ERROR] Failed to read "${params.path}": ${err instanceof Error ? err.message : String(err)}`
1350
+ };
1351
+ }
1171
1352
  const total = lines.length;
1172
1353
  let s = params.start ?? 1;
1173
1354
  let e = Math.min(params.end ?? total, total);
@@ -1180,30 +1361,46 @@ Details: ${res.stderr}` : ""}`
1180
1361
  const content = lines[i - 1] ?? "";
1181
1362
  out.push(`${i}|${content}`);
1182
1363
  }
1364
+ if (out.length > AGENT_CONFIG.MAX_READ_LINES) {
1365
+ const truncated = out.slice(0, AGENT_CONFIG.MAX_READ_LINES);
1366
+ truncated.push(`... [truncated: showing ${AGENT_CONFIG.MAX_READ_LINES} of ${out.length} lines]`);
1367
+ return { lines: truncated };
1368
+ }
1183
1369
  return { lines: out };
1184
1370
  }
1185
- async analyse(params) {
1186
- const abs = resolveUnderRepo(this.repoRoot, params.path);
1371
+ async listDirectory(params) {
1372
+ let abs;
1373
+ try {
1374
+ abs = resolveUnderRepo(this.repoRoot, params.path);
1375
+ } catch {
1376
+ return [];
1377
+ }
1187
1378
  const stat = await import_promises2.default.stat(abs).catch(() => null);
1188
1379
  if (!stat || !stat.isDirectory()) {
1189
1380
  return [];
1190
1381
  }
1191
- const maxResults = params.maxResults ?? 100;
1192
- const maxDepth = params.maxDepth ?? 2;
1382
+ const maxResults = params.maxResults ?? AGENT_CONFIG.MAX_OUTPUT_LINES;
1383
+ const maxDepth = params.maxDepth ?? AGENT_CONFIG.MAX_LIST_DEPTH;
1193
1384
  const regex = params.pattern ? new RegExp(params.pattern) : null;
1194
1385
  const results = [];
1386
+ let timedOut = false;
1387
+ const startTime = Date.now();
1195
1388
  async function walk(dir, depth) {
1389
+ if (Date.now() - startTime > AGENT_CONFIG.LIST_TIMEOUT_MS) {
1390
+ timedOut = true;
1391
+ return;
1392
+ }
1196
1393
  if (depth > maxDepth || results.length >= maxResults) return;
1197
1394
  const entries = await import_promises2.default.readdir(dir, { withFileTypes: true });
1198
1395
  for (const entry of entries) {
1199
- const full = import_path3.default.join(dir, entry.name);
1396
+ if (timedOut || results.length >= maxResults) break;
1397
+ const full = import_path4.default.join(dir, entry.name);
1200
1398
  const rel = toRepoRelative(abs, full).replace(/^[.][/\\]?/, "");
1201
- if (DEFAULT_EXCLUDES.some((ex) => rel.split(import_path3.default.sep).includes(ex))) continue;
1399
+ if (DEFAULT_EXCLUDES.some((ex) => rel.split(import_path4.default.sep).includes(ex))) continue;
1202
1400
  if (regex && !regex.test(entry.name)) continue;
1203
- if (results.length >= maxResults) break;
1204
1401
  results.push({
1205
1402
  name: entry.name,
1206
- path: toRepoRelative(import_path3.default.resolve(""), full),
1403
+ path: toRepoRelative(import_path4.default.resolve(""), full),
1207
1404
  // relative display
1208
1405
  type: entry.isDirectory() ? "dir" : "file",
1209
1406
  depth
@@ -1218,12 +1415,103 @@ Details: ${res.stderr}` : ""}`
1218
1415
  }
1219
1416
  };
1220
1417
 
1221
- // tools/warp_grep/core.ts
1418
+ // tools/warp_grep/providers/remote.ts
1419
+ var RemoteCommandsProvider = class {
1420
+ constructor(repoRoot, commands) {
1421
+ this.repoRoot = repoRoot;
1422
+ this.commands = commands;
1423
+ }
1424
+ /**
1425
+ * Run grep command and parse ripgrep output
1426
+ */
1427
+ async grep(params) {
1428
+ try {
1429
+ const stdout = await this.commands.grep(params.pattern, params.path, params.glob);
1430
+ const lines = (stdout || "").trim().split(/\r?\n/).filter((l) => l.length > 0);
1431
+ if (lines.length > AGENT_CONFIG.MAX_OUTPUT_LINES) {
1432
+ return {
1433
+ lines: [],
1434
+ error: "Query not specific enough - too many results returned. Try a more specific pattern."
1435
+ };
1436
+ }
1437
+ return { lines };
1438
+ } catch (error) {
1439
+ return {
1440
+ lines: [],
1441
+ error: `[GREP ERROR] ${error instanceof Error ? error.message : String(error)}`
1442
+ };
1443
+ }
1444
+ }
1445
+ /**
1446
+ * Read file and add line numbers
1447
+ */
1448
+ async read(params) {
1449
+ const start = params.start ?? 1;
1450
+ const end = params.end ?? 1e6;
1451
+ try {
1452
+ const stdout = await this.commands.read(params.path, start, end);
1453
+ const contentLines = (stdout || "").split("\n");
1454
+ if (contentLines.length > 0 && contentLines[contentLines.length - 1] === "") {
1455
+ contentLines.pop();
1456
+ }
1457
+ const lines = contentLines.map((content, idx) => `${start + idx}|${content}`);
1458
+ if (lines.length > AGENT_CONFIG.MAX_READ_LINES) {
1459
+ const truncated = lines.slice(0, AGENT_CONFIG.MAX_READ_LINES);
1460
+ truncated.push(`... [truncated: showing ${AGENT_CONFIG.MAX_READ_LINES} of ${lines.length} lines]`);
1461
+ return { lines: truncated };
1462
+ }
1463
+ return { lines };
1464
+ } catch (error) {
1465
+ return {
1466
+ lines: [],
1467
+ error: `[READ ERROR] ${error instanceof Error ? error.message : String(error)}`
1468
+ };
1469
+ }
1470
+ }
1471
+ /**
1472
+ * List directory and parse find output
1473
+ */
1474
+ async listDirectory(params) {
1475
+ const maxDepth = params.maxDepth ?? AGENT_CONFIG.MAX_LIST_DEPTH;
1476
+ const maxResults = params.maxResults ?? AGENT_CONFIG.MAX_OUTPUT_LINES;
1477
+ try {
1478
+ const stdout = await this.commands.listDir(params.path, maxDepth);
1479
+ const paths = (stdout || "").trim().split(/\r?\n/).filter((p) => p.length > 0);
1480
+ const regex = params.pattern ? new RegExp(params.pattern) : null;
1481
+ const entries = [];
1482
+ for (const fullPath of paths) {
1483
+ if (fullPath === params.path || fullPath === this.repoRoot) continue;
1484
+ const name = fullPath.split("/").pop() || "";
1485
+ if (regex && !regex.test(name)) continue;
1486
+ let relativePath = fullPath;
1487
+ if (fullPath.startsWith(this.repoRoot)) {
1488
+ relativePath = fullPath.slice(this.repoRoot.length).replace(/^\//, "");
1489
+ }
1490
+ const depth = relativePath.split("/").filter(Boolean).length - 1;
1491
+ const hasExtension = name.includes(".") && !name.startsWith(".");
1492
+ const type = hasExtension ? "file" : "dir";
1493
+ entries.push({
1494
+ name,
1495
+ path: relativePath,
1496
+ type,
1497
+ depth: Math.max(0, depth)
1498
+ });
1499
+ if (entries.length >= maxResults) break;
1500
+ }
1501
+ return entries;
1502
+ } catch (error) {
1503
+ return [];
1504
+ }
1505
+ }
1506
+ };
1507
+
1508
+ // tools/warp_grep/client.ts
1222
1509
  var WarpGrepClient = class {
1223
1510
  config;
1224
1511
  constructor(config = {}) {
1225
1512
  this.config = {
1226
- apiKey: config.apiKey,
1513
+ morphApiKey: config.morphApiKey,
1514
+ morphApiUrl: config.morphApiUrl,
1227
1515
  debug: config.debug,
1228
1516
  timeout: config.timeout,
1229
1517
  retryConfig: config.retryConfig
@@ -1251,38 +1539,50 @@ var WarpGrepClient = class {
1251
1539
  * ```
1252
1540
  */
1253
1541
  async execute(input) {
1254
- const provider = input.provider ?? new LocalRipgrepProvider(input.repoRoot, input.excludes);
1255
- const result = await runWarpGrep({
1256
- query: input.query,
1257
- repoRoot: input.repoRoot,
1258
- provider,
1259
- excludes: input.excludes,
1260
- includes: input.includes,
1261
- debug: input.debug ?? this.config.debug ?? false,
1262
- apiKey: this.config.apiKey
1263
- });
1264
- const finish = result.finish;
1265
- if (result.terminationReason !== "completed" || !finish?.metadata) {
1266
- return {
1267
- success: false,
1268
- error: "Search did not complete"
1269
- };
1270
- }
1271
- const contexts = (finish.resolved ?? []).map((r) => ({
1272
- file: r.path,
1273
- content: r.content
1274
- }));
1275
- return {
1276
- success: true,
1277
- contexts,
1278
- summary: finish.payload
1279
- };
1542
+ return executeToolCall(
1543
+ { query: input.query },
1544
+ {
1545
+ repoRoot: input.repoRoot,
1546
+ remoteCommands: input.remoteCommands,
1547
+ provider: input.provider,
1548
+ excludes: input.excludes,
1549
+ includes: input.includes,
1550
+ debug: input.debug ?? this.config.debug,
1551
+ morphApiKey: this.config.morphApiKey,
1552
+ morphApiUrl: this.config.morphApiUrl,
1553
+ retryConfig: this.config.retryConfig
1554
+ }
1555
+ );
1280
1556
  }
1281
1557
  };
1282
1558
  async function executeWarpGrep(input, config) {
1283
1559
  const client = new WarpGrepClient(config);
1284
1560
  return client.execute(input);
1285
1561
  }
1562
+ async function executeToolCall(input, config) {
1563
+ const parsed = typeof input === "string" ? JSON.parse(input) : input;
1564
+ const provider = config.remoteCommands ? new RemoteCommandsProvider(config.repoRoot, config.remoteCommands) : config.provider ?? new LocalRipgrepProvider(config.repoRoot, config.excludes);
1565
+ const result = await runWarpGrep({
1566
+ query: parsed.query,
1567
+ repoRoot: config.repoRoot,
1568
+ provider,
1569
+ excludes: config.excludes,
1570
+ includes: config.includes,
1571
+ debug: config.debug ?? false,
1572
+ morphApiKey: config.morphApiKey,
1573
+ morphApiUrl: config.morphApiUrl,
1574
+ retryConfig: config.retryConfig
1575
+ });
1576
+ const finish = result.finish;
1577
+ if (result.terminationReason !== "completed" || !finish?.metadata) {
1578
+ return { success: false, error: "Search did not complete" };
1579
+ }
1580
+ const contexts = (finish.resolved ?? []).map((r) => ({
1581
+ file: r.path,
1582
+ content: r.content
1583
+ }));
1584
+ return { success: true, contexts, summary: finish.payload };
1585
+ }
1286
1586
  function formatResult(result) {
1287
1587
  if (!result.success) {
1288
1588
  return `Search failed: ${result.error}`;
@@ -1310,48 +1610,39 @@ function formatResult(result) {
1310
1610
  var WARP_GREP_TOOL_NAME = "warpgrep_codebase_search";
1311
1611
  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.';
1312
1612
 
1313
- // tools/warp_grep/openai.ts
1314
- var TOOL_PARAMETERS = {
1613
+ // tools/warp_grep/anthropic.ts
1614
+ var anthropic_exports = {};
1615
+ __export(anthropic_exports, {
1616
+ createWarpGrepTool: () => createWarpGrepTool,
1617
+ execute: () => execute,
1618
+ formatResult: () => formatResult,
1619
+ getSystemPrompt: () => getSystemPrompt,
1620
+ warpGrepTool: () => warpGrepTool
1621
+ });
1622
+ var INPUT_SCHEMA = {
1315
1623
  type: "object",
1316
1624
  properties: {
1317
1625
  query: { type: "string", description: "Free-form repository question" }
1318
1626
  },
1319
1627
  required: ["query"]
1320
1628
  };
1629
+ var warpGrepTool = {
1630
+ name: WARP_GREP_TOOL_NAME,
1631
+ description: WARP_GREP_DESCRIPTION,
1632
+ input_schema: INPUT_SCHEMA
1633
+ };
1321
1634
  async function execute(input, config) {
1322
- const parsed = typeof input === "string" ? JSON.parse(input) : input;
1323
- const provider = config.provider ?? new LocalRipgrepProvider(config.repoRoot, config.excludes);
1324
- const result = await runWarpGrep({
1325
- query: parsed.query,
1326
- repoRoot: config.repoRoot,
1327
- provider,
1328
- excludes: config.excludes,
1329
- includes: config.includes,
1330
- debug: config.debug ?? false,
1331
- apiKey: config.apiKey
1332
- });
1333
- const finish = result.finish;
1334
- if (result.terminationReason !== "completed" || !finish?.metadata) {
1335
- return { success: false, error: "Search did not complete" };
1336
- }
1337
- const contexts = (finish.resolved ?? []).map((r) => ({
1338
- file: r.path,
1339
- content: r.content
1340
- }));
1341
- return { success: true, contexts, summary: finish.payload };
1635
+ return executeToolCall(input, config);
1342
1636
  }
1343
- function createMorphWarpGrepTool(config) {
1637
+ function createWarpGrepTool(config) {
1344
1638
  const tool2 = {
1345
- type: "function",
1346
- function: {
1347
- name: config.name ?? WARP_GREP_TOOL_NAME,
1348
- description: config.description ?? WARP_GREP_DESCRIPTION,
1349
- parameters: TOOL_PARAMETERS
1350
- }
1639
+ name: config.name ?? WARP_GREP_TOOL_NAME,
1640
+ description: config.description ?? WARP_GREP_DESCRIPTION,
1641
+ input_schema: INPUT_SCHEMA
1351
1642
  };
1352
1643
  return Object.assign(tool2, {
1353
1644
  execute: async (input) => {
1354
- return execute(input, config);
1645
+ return executeToolCall(input, config);
1355
1646
  },
1356
1647
  formatResult: (result) => {
1357
1648
  return formatResult(result);
@@ -1362,45 +1653,46 @@ function createMorphWarpGrepTool(config) {
1362
1653
  });
1363
1654
  }
1364
1655
 
1365
- // tools/warp_grep/anthropic.ts
1366
- var INPUT_SCHEMA = {
1656
+ // tools/warp_grep/openai.ts
1657
+ var openai_exports = {};
1658
+ __export(openai_exports, {
1659
+ createWarpGrepTool: () => createWarpGrepTool2,
1660
+ default: () => openai_default,
1661
+ execute: () => execute2,
1662
+ formatResult: () => formatResult,
1663
+ getSystemPrompt: () => getSystemPrompt,
1664
+ warpGrepTool: () => warpGrepTool2
1665
+ });
1666
+ var TOOL_PARAMETERS = {
1367
1667
  type: "object",
1368
1668
  properties: {
1369
1669
  query: { type: "string", description: "Free-form repository question" }
1370
1670
  },
1371
1671
  required: ["query"]
1372
1672
  };
1373
- async function execute2(input, config) {
1374
- const parsed = typeof input === "string" ? JSON.parse(input) : input;
1375
- const provider = config.provider ?? new LocalRipgrepProvider(config.repoRoot, config.excludes);
1376
- const result = await runWarpGrep({
1377
- query: parsed.query,
1378
- repoRoot: config.repoRoot,
1379
- provider,
1380
- excludes: config.excludes,
1381
- includes: config.includes,
1382
- debug: config.debug ?? false,
1383
- apiKey: config.apiKey
1384
- });
1385
- const finish = result.finish;
1386
- if (result.terminationReason !== "completed" || !finish?.metadata) {
1387
- return { success: false, error: "Search did not complete" };
1673
+ var warpGrepTool2 = {
1674
+ type: "function",
1675
+ function: {
1676
+ name: WARP_GREP_TOOL_NAME,
1677
+ description: WARP_GREP_DESCRIPTION,
1678
+ parameters: TOOL_PARAMETERS
1388
1679
  }
1389
- const contexts = (finish.resolved ?? []).map((r) => ({
1390
- file: r.path,
1391
- content: r.content
1392
- }));
1393
- return { success: true, contexts, summary: finish.payload };
1680
+ };
1681
+ async function execute2(input, config) {
1682
+ return executeToolCall(input, config);
1394
1683
  }
1395
- function createMorphWarpGrepTool2(config) {
1684
+ function createWarpGrepTool2(config) {
1396
1685
  const tool2 = {
1397
- name: config.name ?? WARP_GREP_TOOL_NAME,
1398
- description: config.description ?? WARP_GREP_DESCRIPTION,
1399
- input_schema: INPUT_SCHEMA
1686
+ type: "function",
1687
+ function: {
1688
+ name: config.name ?? WARP_GREP_TOOL_NAME,
1689
+ description: config.description ?? WARP_GREP_DESCRIPTION,
1690
+ parameters: TOOL_PARAMETERS
1691
+ }
1400
1692
  };
1401
1693
  return Object.assign(tool2, {
1402
1694
  execute: async (input) => {
1403
- return execute2(input, config);
1695
+ return executeToolCall(input, config);
1404
1696
  },
1405
1697
  formatResult: (result) => {
1406
1698
  return formatResult(result);
@@ -1410,53 +1702,113 @@ function createMorphWarpGrepTool2(config) {
1410
1702
  }
1411
1703
  });
1412
1704
  }
1705
+ var openai_default = warpGrepTool2;
1413
1706
 
1414
1707
  // tools/warp_grep/vercel.ts
1708
+ var vercel_exports = {};
1709
+ __export(vercel_exports, {
1710
+ createWarpGrepTool: () => createWarpGrepTool3,
1711
+ default: () => vercel_default,
1712
+ execute: () => execute3,
1713
+ formatResult: () => formatResult,
1714
+ getSystemPrompt: () => getSystemPrompt
1715
+ });
1415
1716
  var import_ai = require("ai");
1416
1717
  var import_zod = require("zod");
1417
- var warpGrepSchema = import_zod.z.object({
1418
- query: import_zod.z.string().describe("Free-form repository question")
1419
- });
1420
- function createMorphWarpGrepTool3(config) {
1718
+ async function execute3(input, config) {
1719
+ return executeToolCall(input, config);
1720
+ }
1721
+ function createWarpGrepTool3(config) {
1722
+ const schema = import_zod.z.object({
1723
+ query: import_zod.z.string().describe("Free-form repository question")
1724
+ });
1421
1725
  return (0, import_ai.tool)({
1422
1726
  description: config.description ?? WARP_GREP_DESCRIPTION,
1423
- inputSchema: warpGrepSchema,
1727
+ inputSchema: schema,
1424
1728
  execute: async (params) => {
1425
- const provider = config.provider ?? new LocalRipgrepProvider(config.repoRoot, config.excludes);
1426
- const result = await runWarpGrep({
1427
- query: params.query,
1428
- repoRoot: config.repoRoot,
1429
- provider,
1430
- excludes: config.excludes,
1431
- includes: config.includes,
1432
- debug: config.debug ?? false,
1433
- apiKey: config.apiKey
1434
- });
1435
- const finish = result.finish;
1436
- if (result.terminationReason !== "completed" || !finish?.metadata) {
1437
- return { success: false, error: "Search did not complete" };
1729
+ const result = await executeToolCall(params, config);
1730
+ if (!result.success) {
1731
+ throw new Error(`Failed to search codebase: ${result.error}`);
1438
1732
  }
1439
- const contexts = (finish.resolved ?? []).map((r) => ({
1440
- file: r.path,
1441
- content: r.content
1442
- }));
1443
- return { success: true, contexts, summary: finish.payload };
1733
+ return {
1734
+ success: true,
1735
+ contexts: result.contexts,
1736
+ summary: result.summary
1737
+ };
1738
+ }
1739
+ });
1740
+ }
1741
+ var vercel_default = createWarpGrepTool3;
1742
+
1743
+ // tools/warp_grep/gemini.ts
1744
+ var gemini_exports = {};
1745
+ __export(gemini_exports, {
1746
+ createMorphWarpGrepTool: () => createMorphWarpGrepTool,
1747
+ default: () => gemini_default,
1748
+ execute: () => execute4,
1749
+ formatResult: () => formatResult,
1750
+ getSystemPrompt: () => getSystemPrompt,
1751
+ warpGrepFunctionDeclaration: () => warpGrepFunctionDeclaration
1752
+ });
1753
+ var import_generative_ai = require("@google/generative-ai");
1754
+ var TOOL_PARAMETERS2 = {
1755
+ type: import_generative_ai.SchemaType.OBJECT,
1756
+ properties: {
1757
+ query: {
1758
+ type: import_generative_ai.SchemaType.STRING,
1759
+ description: "Free-form repository question"
1760
+ }
1761
+ },
1762
+ required: ["query"]
1763
+ };
1764
+ var warpGrepFunctionDeclaration = {
1765
+ name: WARP_GREP_TOOL_NAME,
1766
+ description: WARP_GREP_DESCRIPTION,
1767
+ parameters: TOOL_PARAMETERS2
1768
+ };
1769
+ async function execute4(input, config) {
1770
+ return executeToolCall(input, config);
1771
+ }
1772
+ function createMorphWarpGrepTool(config) {
1773
+ const declaration = {
1774
+ name: config.name ?? WARP_GREP_TOOL_NAME,
1775
+ description: config.description ?? WARP_GREP_DESCRIPTION,
1776
+ parameters: TOOL_PARAMETERS2
1777
+ };
1778
+ return Object.assign(declaration, {
1779
+ execute: async (input) => {
1780
+ return executeToolCall(input, config);
1781
+ },
1782
+ formatResult: (result) => {
1783
+ return formatResult(result);
1784
+ },
1785
+ getSystemPrompt: () => {
1786
+ return getSystemPrompt();
1444
1787
  }
1445
1788
  });
1446
1789
  }
1790
+ var gemini_default = warpGrepFunctionDeclaration;
1447
1791
  // Annotate the CommonJS export names for ESM import in node:
1448
1792
  0 && (module.exports = {
1449
1793
  LocalRipgrepProvider,
1794
+ RemoteCommandsProvider,
1450
1795
  WARP_GREP_DESCRIPTION,
1451
1796
  WARP_GREP_SYSTEM_PROMPT,
1452
1797
  WARP_GREP_TOOL_NAME,
1453
1798
  WarpGrepClient,
1454
- createAnthropicWarpGrepTool,
1455
- createOpenAIWarpGrepTool,
1456
- createVercelWarpGrepTool,
1799
+ anthropic,
1800
+ executeToolCall,
1457
1801
  executeWarpGrep,
1458
1802
  formatResult,
1803
+ gemini,
1459
1804
  getSystemPrompt,
1460
- runWarpGrep
1805
+ normalizeFinishFiles,
1806
+ openai,
1807
+ readFinishFiles,
1808
+ runWarpGrep,
1809
+ toolGrep,
1810
+ toolListDirectory,
1811
+ toolRead,
1812
+ vercel
1461
1813
  });
1462
1814
  //# sourceMappingURL=index.cjs.map