@probelabs/probe 0.6.0-rc265 → 0.6.0-rc267

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 (37) hide show
  1. package/bin/binaries/probe-v0.6.0-rc267-aarch64-apple-darwin.tar.gz +0 -0
  2. package/bin/binaries/probe-v0.6.0-rc267-aarch64-unknown-linux-musl.tar.gz +0 -0
  3. package/bin/binaries/probe-v0.6.0-rc267-x86_64-apple-darwin.tar.gz +0 -0
  4. package/bin/binaries/probe-v0.6.0-rc267-x86_64-pc-windows-msvc.zip +0 -0
  5. package/bin/binaries/probe-v0.6.0-rc267-x86_64-unknown-linux-musl.tar.gz +0 -0
  6. package/build/agent/ProbeAgent.js +644 -1442
  7. package/build/agent/engines/enhanced-vercel.js +0 -7
  8. package/build/agent/index.js +3941 -5940
  9. package/build/agent/mcp/index.js +6 -15
  10. package/build/agent/mcp/xmlBridge.js +24 -324
  11. package/build/agent/tasks/index.js +0 -1
  12. package/build/agent/tools.js +11 -181
  13. package/build/index.js +13 -35
  14. package/build/tools/common.js +15 -707
  15. package/build/tools/executePlan.js +2 -2
  16. package/build/tools/index.js +8 -11
  17. package/cjs/agent/ProbeAgent.cjs +3734 -5831
  18. package/cjs/index.cjs +4797 -6869
  19. package/package.json +1 -1
  20. package/src/agent/ProbeAgent.js +644 -1442
  21. package/src/agent/engines/enhanced-vercel.js +0 -7
  22. package/src/agent/index.js +10 -2
  23. package/src/agent/mcp/index.js +6 -15
  24. package/src/agent/mcp/xmlBridge.js +24 -324
  25. package/src/agent/tasks/index.js +0 -1
  26. package/src/agent/tools.js +11 -181
  27. package/src/index.js +13 -35
  28. package/src/tools/common.js +15 -707
  29. package/src/tools/executePlan.js +2 -2
  30. package/src/tools/index.js +8 -11
  31. package/bin/binaries/probe-v0.6.0-rc265-aarch64-apple-darwin.tar.gz +0 -0
  32. package/bin/binaries/probe-v0.6.0-rc265-aarch64-unknown-linux-musl.tar.gz +0 -0
  33. package/bin/binaries/probe-v0.6.0-rc265-x86_64-apple-darwin.tar.gz +0 -0
  34. package/bin/binaries/probe-v0.6.0-rc265-x86_64-pc-windows-msvc.zip +0 -0
  35. package/bin/binaries/probe-v0.6.0-rc265-x86_64-unknown-linux-musl.tar.gz +0 -0
  36. package/build/agent/xmlParsingUtils.js +0 -221
  37. package/src/agent/xmlParsingUtils.js +0 -221
@@ -5,8 +5,6 @@
5
5
 
6
6
  import { z } from 'zod';
7
7
  import { resolve, isAbsolute } from 'path';
8
- import { editSchema, createSchema, multiEditSchema } from './edit.js';
9
- import { taskSchema } from '../agent/tasks/taskTool.js';
10
8
 
11
9
  // Common schemas for tool parameters (used for internal execution after XML parsing)
12
10
  export const searchSchema = z.object({
@@ -51,6 +49,20 @@ export const useSkillSchema = z.object({
51
49
  name: z.string().describe('Skill name to load and activate.')
52
50
  });
53
51
 
52
+ export const listFilesSchema = z.object({
53
+ directory: z.string().optional().describe('Directory to list files from. Defaults to current directory.')
54
+ });
55
+
56
+ export const searchFilesSchema = z.object({
57
+ pattern: z.string().describe('Glob pattern to search for (e.g., "**/*.js", "*.md")'),
58
+ directory: z.string().optional().describe('Directory to search in. Defaults to current directory.'),
59
+ recursive: z.boolean().optional().default(true).describe('Whether to search recursively')
60
+ });
61
+
62
+ export const readImageSchema = z.object({
63
+ path: z.string().describe('Path to the image file to read. Supports png, jpg, jpeg, webp, bmp, and svg formats.')
64
+ });
65
+
54
66
  export const bashSchema = z.object({
55
67
  command: z.string().describe('The bash command to execute'),
56
68
  workingDirectory: z.string().optional().describe('Directory to execute the command in (optional)'),
@@ -135,301 +147,7 @@ export const attemptCompletionSchema = {
135
147
  };
136
148
 
137
149
 
138
- // Tool descriptions for the system prompt (using XML format)
139
-
140
- export const searchToolDefinition = `
141
- ## search
142
- Description: Search code in the repository. You may provide a free-form question about the code or a concise Elasticsearch-style keyword query (field based queries, e.g. "filename:..." NOT supported).
143
- Note: This tool may internally use a dedicated search subagent when search delegation is enabled. This is separate from the "delegate" tool and does not require an explicit delegate call.
144
-
145
- You need to focus on main keywords when constructing the query, and always use elastic search syntax like OR AND and brackets to group keywords.
146
-
147
- **Session Management & Caching:**
148
- - Ensure not to re-read the same symbols twice - reuse context from previous tool calls
149
- - Probe returns a session ID on first run - reuse it for subsequent calls to avoid redundant searches
150
- - Once data is returned, it's cached and won't return on next runs (this is expected behavior)
151
-
152
- Parameters:
153
- - query: (required) Search query. Free-form questions are accepted, but for best results prefer Elasticsearch-style syntax with quotes for exact matches ("functionName"), AND/OR for boolean logic, - for negation, + for important terms.
154
- - path: (optional, default: '.') Path to search in. All dependencies located in /dep folder, under language sub folders, like this: "/dep/go/github.com/owner/repo", "/dep/js/package_name", or "/dep/rust/cargo_name" etc.
155
- - exact: (optional, default: false) Set to true for precise symbol lookup without stemming/tokenization. Use when you know the exact symbol name (e.g., "getUserData" matches only "getUserData", not "get", "user", "data").
156
- - session: (optional) Session ID for pagination. Pass the session ID returned from a previous search to get the next page of results. Results already shown are automatically excluded.
157
- - nextPage: (optional, default: false) Set to true when requesting the next page of results. Requires passing the same session ID from the previous search.
158
-
159
- **Workflow:** Always start with search, then use extract for detailed context when needed.
160
-
161
- Usage Example:
162
-
163
- <examples>
164
-
165
- User: Where is the login logic?
166
- Assistant workflow:
167
- 1. <search>
168
- <query>login AND auth AND token</query>
169
- <path>.</path>
170
- </search>
171
- 2. Now lets look closer: <extract>
172
- <targets>session.rs#AuthService.login auth.rs:2-100</targets>
173
- </extract>
174
-
175
- User: How to calculate the total amount in the payments module?
176
- <search>
177
- <query>calculate AND payment</query>
178
- <path>src/utils</path>
179
- </search>
180
-
181
- User: How do the user authentication and authorization work?
182
- <search>
183
- <query>+user AND (authentication OR authorization OR authz)</query>
184
- <path>.</path>
185
- </search>
186
-
187
- User: Find all react imports in the project.
188
- <search>
189
- <query>"import" AND "react"</query>
190
- <path>.</path>
191
- </search>
192
-
193
- User: Find how decompound library works?
194
- <search>
195
- <query>decompound</query>
196
- <path>/dep/rust/decompound</path>
197
- </search>
198
-
199
- </examples>
200
- `;
201
-
202
- export const queryToolDefinition = `
203
- ## query
204
- Description: Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.
205
- Parameters:
206
- - pattern: (required) AST pattern to search for. Use $NAME for variable names, $$$PARAMS for parameter lists, etc.
207
- - path: (optional, default: '.') Path to search in.
208
- - language: (optional, default: 'rust') Programming language to use for parsing.
209
- - allow_tests: (optional, default: true) Allow test files in search results (true/false).
210
- Usage Example:
211
-
212
- <examples>
213
-
214
- <query>
215
- <pattern>function $FUNC($$$PARAMS) { $$$BODY }</pattern>
216
- <path>src/parser</path>
217
- <language>js</language>
218
- </query>
219
-
220
- </examples>
221
- `;
222
-
223
- export const extractToolDefinition = `
224
- ## extract
225
- Description: Extract code blocks from files based on file paths and optional line numbers. Use this tool to see complete context after finding relevant files. It can be used to read full files as well.
226
- Full file extraction should be the LAST RESORT! Always prefer search.
227
-
228
- **Multiple Extraction:** You can extract multiple symbols/files in one call by providing multiple file paths separated by spaces.
229
-
230
- **Session Awareness:** Reuse context from previous tool calls. Don't re-extract the same symbols you already have.
231
-
232
- Parameters:
233
- - targets: (required) File paths or symbols to extract from. Formats: "file.js" (whole file), "file.js:42" (code block at line 42), "file.js:10-20" (lines 10-20), "file.js#funcName" (specific symbol). Multiple targets separated by spaces.
234
- - input_content: (optional) Text content to extract file paths from (alternative to targets for processing diffs/logs).
235
- - allow_tests: (optional, default: true) Include test files in extraction results.
236
-
237
- Usage Example:
238
-
239
- <examples>
240
-
241
- User: Where is the login logic? (After search found relevant files)
242
- <extract>
243
- <targets>session.rs#AuthService.login auth.rs:2-100 config.rs#DatabaseConfig</targets>
244
- </extract>
245
-
246
- User: How does error handling work? (After search identified files)
247
- <extract>
248
- <targets>error.rs#ErrorType utils.rs#handle_error src/main.rs:50-80</targets>
249
- </extract>
250
-
251
- User: How RankManager works
252
- <extract>
253
- <targets>src/search/ranking.rs#RankManager</targets>
254
- </extract>
255
-
256
- User: Lets read the whole file
257
- <extract>
258
- <targets>src/search/ranking.rs</targets>
259
- </extract>
260
-
261
- User: Read the first 10 lines of the file
262
- <extract>
263
- <targets>src/search/ranking.rs:1-10</targets>
264
- </extract>
265
-
266
- User: Read file inside the dependency
267
- <extract>
268
- <targets>/dep/go/github.com/gorilla/mux/router.go</targets>
269
- </extract>
270
-
271
- </examples>
272
-
273
- **Edit Integration:** The line numbers shown in extract output (e.g. "42 | code") can be used directly with the edit tool's start_line/end_line parameters for precise line-targeted editing. To edit inside a large function: extract it by symbol name first (e.g. "file.js#myFunction"), then use the line numbers from the output to make surgical edits with start_line/end_line.
274
- `;
275
-
276
- export const delegateToolDefinition = `
277
- ## delegate
278
- Description: Automatically delegate big distinct tasks to specialized probe subagents within the agentic loop. Use this when you recognize that a user's request involves multiple large, distinct components that would benefit from parallel processing or specialized focus. The AI agent should automatically identify opportunities for task separation and use delegation without explicit user instruction.
279
-
280
- Parameters:
281
- - task: (required) A complete, self-contained task that can be executed independently by a subagent. Should be specific and focused on one area of expertise.
282
-
283
- Usage Pattern:
284
- When the AI agent encounters complex multi-part requests, it should automatically break them down and delegate:
285
-
286
- <delegate>
287
- <task>Analyze all authentication and authorization code in the codebase for security vulnerabilities and provide specific remediation recommendations</task>
288
- </delegate>
289
-
290
- <delegate>
291
- <task>Review database queries and API endpoints for performance bottlenecks and suggest optimization strategies</task>
292
- </delegate>
293
-
294
- The agent uses this tool automatically when it identifies that work can be separated into distinct, parallel tasks for more efficient processing.
295
- `;
296
-
297
- export const attemptCompletionToolDefinition = `
298
- ## attempt_completion
299
- Description: Use this tool ONLY when the task is fully complete and you have received confirmation of success for all previous tool uses. Presents the final result to the user. You can provide your response directly inside the XML tags without any parameter wrapper.
300
- Parameters:
301
- - No validation required - provide your complete answer directly inside the XML tags.
302
- Usage Example:
303
- <attempt_completion>
304
- I have refactored the search module according to the requirements and verified the tests pass. The module now uses the new BM25 ranking algorithm and has improved error handling.
305
- </attempt_completion>
306
- `;
307
-
308
- export const analyzeAllToolDefinition = `
309
- ## analyze_all
310
- Description: Intelligent bulk data analysis tool. Process ALL data matching your question using a 3-phase approach:
311
- 1. **PLANNING**: AI analyzes your question and determines the optimal search strategy
312
- 2. **PROCESSING**: Map-reduce processes all matching data in parallel chunks
313
- 3. **SYNTHESIS**: Comprehensive answer with evidence and organization
314
-
315
- **Use this for questions requiring 100% data coverage:**
316
- - "What features are customers using?"
317
- - "List all API endpoints in the codebase"
318
- - "Summarize the error handling patterns"
319
- - "Count all TODO comments and their contexts"
320
-
321
- **Do NOT use for:**
322
- - Simple searches where a sample is sufficient
323
- - Finding a specific function or class
324
- - Quick exploration (use search instead)
325
-
326
- **WARNING:** Makes multiple LLM calls - slower and costlier than search.
327
-
328
- Parameters:
329
- - question: (required) Free-form question to answer - the AI determines the best search strategy automatically
330
- - path: (optional) Directory to search in (default: current directory)
331
-
332
- <examples>
333
-
334
- User: What are all the different tools available in this codebase?
335
- <analyze_all>
336
- <question>What are all the different tools available in this codebase and what do they do?</question>
337
- <path>./src</path>
338
- </analyze_all>
339
-
340
- User: I need to understand all the error handling patterns
341
- <analyze_all>
342
- <question>What error handling patterns are used throughout the codebase? Include examples.</question>
343
- </analyze_all>
344
-
345
- User: Count and categorize all the environment variables
346
- <analyze_all>
347
- <question>What environment variables are used? Categorize them by purpose.</question>
348
- <path>./src</path>
349
- </analyze_all>
350
-
351
- </examples>
352
- `;
353
-
354
- export const bashToolDefinition = `
355
- ## bash
356
- Description: Execute bash commands for system exploration and development tasks. This tool has built-in security with allow/deny lists. By default, only safe read-only commands are allowed for code exploration.
357
-
358
- Parameters:
359
- - command: (required) The bash command to execute
360
- - workingDirectory: (optional) Directory to execute the command in
361
- - timeout: (optional) Command timeout in milliseconds
362
- - env: (optional) Additional environment variables as an object
363
-
364
- Security: Commands are filtered through allow/deny lists for safety:
365
- - Allowed by default: ls, cat, git status, npm list, find, grep, etc.
366
- - Denied by default: rm -rf, sudo, npm install, dangerous system commands
367
-
368
- Usage Examples:
369
-
370
- <examples>
371
-
372
- User: What files are in the src directory?
373
- <bash>
374
- <command>ls -la src/</command>
375
- </bash>
376
-
377
- User: Show me the git status
378
- <bash>
379
- <command>git status</command>
380
- </bash>
381
-
382
- User: Find all TypeScript files
383
- <bash>
384
- <command>find . -name "*.ts" -type f</command>
385
- </bash>
386
-
387
- User: Check installed npm packages
388
- <bash>
389
- <command>npm list --depth=0</command>
390
- </bash>
391
-
392
- User: Search for TODO comments in code
393
- <bash>
394
- <command>grep -r "TODO" src/</command>
395
- </bash>
396
-
397
- User: Show recent git commits
398
- <bash>
399
- <command>git log --oneline -10</command>
400
- </bash>
401
-
402
- User: Check system info
403
- <bash>
404
- <command>uname -a</command>
405
- </bash>
406
-
407
- </examples>
408
- `;
409
-
410
- export const googleSearchToolDefinition = `
411
- ## gemini_google_search (Gemini Built-in)
412
- Description: Web search powered by Google. This is a built-in Gemini capability that automatically searches the web when the model needs current information. The model decides when to search and integrates results directly into its response with source citations.
413
-
414
- This tool is invoked automatically by the model — you do NOT need to use XML tool calls for it. Simply ask questions that require up-to-date or real-world information and the model will search the web as needed.
415
-
416
- Capabilities:
417
- - Real-time web search with grounded citations
418
- - Automatic query generation and result synthesis
419
- - Source attribution with URLs
420
- `;
421
-
422
- export const urlContextToolDefinition = `
423
- ## gemini_url_context (Gemini Built-in)
424
- Description: URL content reader powered by Google. This is a built-in Gemini capability that automatically fetches and analyzes the content of URLs mentioned in the conversation. When you include URLs in your message, the model can read and understand their content.
425
-
426
- This tool is invoked automatically by the model — you do NOT need to use XML tool calls for it. Simply include URLs in your message and the model will fetch and analyze their content.
427
-
428
- Capabilities:
429
- - Fetch and read web page content from URLs in the prompt
430
- - Supports up to 20 URLs per request
431
- - Processes HTML content (does not execute JavaScript)
432
- `;
150
+ // Tool descriptions (used by Vercel tool() definitions)
433
151
 
434
152
  export const searchDescription = 'Search code in the repository. Free-form questions are accepted, but Elasticsearch-style keyword queries work best. Use this tool first for any code-related questions.';
435
153
  export const queryDescription = 'Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.';
@@ -438,261 +156,6 @@ export const delegateDescription = 'Automatically delegate big distinct tasks to
438
156
  export const bashDescription = 'Execute bash commands for system exploration and development tasks. Secure by default with built-in allow/deny lists.';
439
157
  export const analyzeAllDescription = 'Answer questions that require analyzing ALL matching data in the codebase. Use for aggregate questions like "What features exist?", "List all API endpoints", "Count TODO comments". The AI automatically plans the search strategy, processes all results via map-reduce, and synthesizes a comprehensive answer. WARNING: Slower than search - only use when you need complete coverage.';
440
158
 
441
- // Valid tool names that should be parsed as tool calls
442
- // This is the canonical list - all other tool lists should reference this
443
- export const DEFAULT_VALID_TOOLS = [
444
- 'search',
445
- 'query',
446
- 'extract',
447
- 'delegate',
448
- 'analyze_all',
449
- 'execute_plan',
450
- 'cleanup_execute_plan',
451
- 'listSkills',
452
- 'useSkill',
453
- 'listFiles',
454
- 'searchFiles',
455
- 'bash',
456
- 'task',
457
- 'attempt_completion'
458
- ];
459
-
460
- /**
461
- * Build a regex pattern to match any tool tag from the valid tools list
462
- * @param {string[]} tools - List of tool names (defaults to DEFAULT_VALID_TOOLS)
463
- * @returns {RegExp} - Regex pattern to match tool opening tags
464
- */
465
- export function buildToolTagPattern(tools = DEFAULT_VALID_TOOLS) {
466
- // Also include attempt_complete as an alias for attempt_completion
467
- const allTools = [...tools];
468
- if (allTools.includes('attempt_completion') && !allTools.includes('attempt_complete')) {
469
- allTools.push('attempt_complete');
470
- }
471
- const escaped = allTools.map(t => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
472
- return new RegExp(`<(${escaped.join('|')})>`);
473
- }
474
-
475
- /**
476
- * Get valid parameter names for a specific tool from its schema
477
- * @param {string} toolName - Name of the tool
478
- * @returns {string[]} - Array of valid parameter names for this tool
479
- */
480
- function getValidParamsForTool(toolName) {
481
- // Map tool names to their schemas (supports both Zod and JSON Schema formats)
482
- const schemaMap = {
483
- search: searchSchema,
484
- query: querySchema,
485
- extract: extractSchema,
486
- delegate: delegateSchema,
487
- analyze_all: analyzeAllSchema,
488
- execute_plan: executePlanSchema,
489
- listSkills: listSkillsSchema,
490
- useSkill: useSkillSchema,
491
- bash: bashSchema,
492
- task: taskSchema,
493
- attempt_completion: attemptCompletionSchema,
494
- edit: editSchema,
495
- create: createSchema,
496
- multi_edit: multiEditSchema
497
- };
498
-
499
- const schema = schemaMap[toolName];
500
- if (!schema) {
501
- // For tools without schema (listFiles, searchFiles), return common params
502
- // These are the shared params that appear across multiple tools
503
- return ['path', 'directory', 'pattern', 'recursive', 'includeHidden', 'task', 'files', 'result'];
504
- }
505
-
506
- // For attempt_completion, it has custom validation, just return 'result'
507
- if (toolName === 'attempt_completion') {
508
- return ['result'];
509
- }
510
-
511
- // Extract keys from Zod schema
512
- if (schema._def && schema._def.shape) {
513
- return Object.keys(schema._def.shape());
514
- }
515
-
516
- // Extract keys from JSON Schema (used by edit and create tools)
517
- if (schema.properties) {
518
- return Object.keys(schema.properties);
519
- }
520
-
521
- // Fallback: return empty array if we can't extract schema keys
522
- return [];
523
- }
524
-
525
- /**
526
- * Unescape standard XML entities in a string value.
527
- * Order matters: &amp; must be decoded LAST to avoid double-decoding
528
- * (e.g., &amp;lt; should become &lt;, not <).
529
- * @param {string} str - The string to unescape
530
- * @returns {string} The unescaped string
531
- */
532
- export function unescapeXmlEntities(str) {
533
- if (typeof str !== 'string') return str;
534
- return str
535
- .replace(/&lt;/g, '<')
536
- .replace(/&gt;/g, '>')
537
- .replace(/&quot;/g, '"')
538
- .replace(/&apos;/g, "'")
539
- .replace(/&amp;/g, '&');
540
- }
541
-
542
- // Parameters that contain arbitrary code/file content — use lastIndexOf for closing tag
543
- // to handle cases where the content itself contains the closing tag string.
544
- const RAW_CONTENT_PARAMS = new Set(['content', 'new_string', 'old_string']);
545
-
546
- // Tools whose content can include their own closing tag string (e.g., file content
547
- // containing </create> or </edit>). Use lastIndexOf for outer tag boundary, same
548
- // strategy already used for attempt_completion.
549
- const LAST_INDEX_TOOLS = new Set(['attempt_completion', 'create', 'edit']);
550
-
551
- // Simple XML parser helper - safer string-based approach
552
- export function parseXmlToolCall(xmlString, validTools = DEFAULT_VALID_TOOLS) {
553
- // Find the tool that appears EARLIEST in the string
554
- // This prevents parameter tags (like <query> inside <analyze_all>) from being matched as tools
555
- let earliestToolName = null;
556
- let earliestOpenIndex = Infinity;
557
-
558
- for (const toolName of validTools) {
559
- const openTag = `<${toolName}>`;
560
- const openIndex = xmlString.indexOf(openTag);
561
- if (openIndex !== -1 && openIndex < earliestOpenIndex) {
562
- earliestOpenIndex = openIndex;
563
- earliestToolName = toolName;
564
- }
565
- }
566
-
567
- // No valid tool found
568
- if (earliestToolName === null) {
569
- return null;
570
- }
571
-
572
- const toolName = earliestToolName;
573
- const openTag = `<${toolName}>`;
574
- const closeTag = `</${toolName}>`;
575
- const openIndex = earliestOpenIndex;
576
-
577
- // For tools that contain arbitrary content (file content, code), use lastIndexOf
578
- // to find the LAST occurrence of the closing tag. This prevents issues where the
579
- // content itself contains the closing tag string (e.g., file content with </create>).
580
- // For other tools, use indexOf from the opening tag position.
581
- let closeIndex;
582
- if (LAST_INDEX_TOOLS.has(toolName)) {
583
- // Find the last occurrence of the closing tag in the entire string
584
- closeIndex = xmlString.lastIndexOf(closeTag);
585
- // Make sure the closing tag is after the opening tag
586
- if (closeIndex !== -1 && closeIndex <= openIndex + openTag.length) {
587
- closeIndex = -1; // Invalid, treat as no closing tag
588
- }
589
- } else {
590
- closeIndex = xmlString.indexOf(closeTag, openIndex + openTag.length);
591
- }
592
-
593
- let hasClosingTag = closeIndex !== -1;
594
-
595
- // If no closing tag found, use content until end of string
596
- // This makes the parser more resilient to AI formatting errors
597
- if (closeIndex === -1) {
598
- closeIndex = xmlString.length;
599
- }
600
-
601
- // Extract the content between tags (or until end if no closing tag)
602
- const innerContent = xmlString.substring(
603
- openIndex + openTag.length,
604
- closeIndex
605
- );
606
-
607
- const params = {};
608
-
609
- // Get valid parameters for this specific tool from its schema
610
- const validParams = getValidParamsForTool(toolName);
611
-
612
- // Parse parameters using string-based approach for better safety
613
- // Only look for parameters that are valid for this specific tool
614
- for (const paramName of validParams) {
615
- const paramOpenTag = `<${paramName}>`;
616
- const paramCloseTag = `</${paramName}>`;
617
-
618
- const paramOpenIndex = innerContent.indexOf(paramOpenTag);
619
- if (paramOpenIndex === -1) {
620
- continue; // Parameter not found
621
- }
622
-
623
- // For raw content params (file content, code), use lastIndexOf to find the
624
- // LAST closing tag — the content itself may contain the closing tag string.
625
- // For other params (file_path, overwrite, etc.), use indexOf (first match).
626
- let paramCloseIndex;
627
- if (RAW_CONTENT_PARAMS.has(paramName)) {
628
- paramCloseIndex = innerContent.lastIndexOf(paramCloseTag);
629
- // Ensure it's after the opening tag
630
- if (paramCloseIndex !== -1 && paramCloseIndex <= paramOpenIndex + paramOpenTag.length) {
631
- paramCloseIndex = -1;
632
- }
633
- } else {
634
- paramCloseIndex = innerContent.indexOf(paramCloseTag, paramOpenIndex + paramOpenTag.length);
635
- }
636
-
637
- // Handle unclosed parameter tags - use content until next tag or end of content
638
- if (paramCloseIndex === -1) {
639
- // Find the next opening tag after this parameter
640
- let nextTagIndex = innerContent.length;
641
- for (const nextParam of validParams) {
642
- const nextOpenTag = `<${nextParam}>`;
643
- const nextIndex = innerContent.indexOf(nextOpenTag, paramOpenIndex + paramOpenTag.length);
644
- if (nextIndex !== -1 && nextIndex < nextTagIndex) {
645
- nextTagIndex = nextIndex;
646
- }
647
- }
648
- paramCloseIndex = nextTagIndex;
649
- }
650
-
651
- const rawValue = innerContent.substring(
652
- paramOpenIndex + paramOpenTag.length,
653
- paramCloseIndex
654
- );
655
-
656
- // For raw content params, preserve whitespace (only strip XML formatting newlines).
657
- // For other params, trim all whitespace.
658
- let paramValue;
659
- if (RAW_CONTENT_PARAMS.has(paramName)) {
660
- paramValue = unescapeXmlEntities(rawValue.replace(/^\n/, '').replace(/\n$/, ''));
661
- } else {
662
- paramValue = unescapeXmlEntities(rawValue.trim());
663
- }
664
-
665
- // Type coercion for non-content params only (content/new_string/old_string must stay strings)
666
- if (!RAW_CONTENT_PARAMS.has(paramName)) {
667
- if (paramValue.toLowerCase() === 'true') {
668
- paramValue = true;
669
- } else if (paramValue.toLowerCase() === 'false') {
670
- paramValue = false;
671
- } else if (!isNaN(paramValue) && paramValue.trim() !== '') {
672
- // Check if it's potentially a number (handle integers and floats)
673
- const num = Number(paramValue);
674
- if (Number.isFinite(num)) { // Use Number.isFinite to avoid Infinity/NaN
675
- paramValue = num;
676
- }
677
- // Keep as string if not a valid finite number
678
- }
679
- }
680
-
681
- params[paramName] = paramValue;
682
- }
683
-
684
- // Special handling for attempt_completion - use entire inner content as result
685
- if (toolName === 'attempt_completion') {
686
- params['result'] = unescapeXmlEntities(innerContent.trim());
687
- // Remove command parameter if it was parsed by generic logic above (legacy compatibility)
688
- if (params.command) {
689
- delete params.command;
690
- }
691
- }
692
-
693
- // Return the parsed tool call
694
- return { toolName, params };
695
- }
696
159
 
697
160
  /**
698
161
  * Creates an improved preview of a message showing start and end portions
@@ -723,161 +186,6 @@ export function createMessagePreview(message, charsPerSide = 200) {
723
186
  return `${start}...${end}`;
724
187
  }
725
188
 
726
- /**
727
- * Detect if the response contains an XML-style tool tag that wasn't recognized
728
- * This helps identify when the AI tried to use a tool that's not in the validTools list
729
- *
730
- * @param {string} xmlString - The XML string to search
731
- * @param {string[]} validTools - List of valid tool names that would have been recognized
732
- * @returns {string|null} - The unrecognized tool name, or null if no unrecognized tools found
733
- */
734
- export function detectUnrecognizedToolCall(xmlString, validTools) {
735
- if (!xmlString || typeof xmlString !== 'string') {
736
- return null;
737
- }
738
-
739
- // Common tool names that AI might try to use (these should appear as top-level tags)
740
- const knownToolNames = [
741
- 'search', 'query', 'extract', 'listFiles', 'searchFiles',
742
- 'listSkills', 'useSkill', 'readImage', 'edit',
743
- 'create', 'multi_edit', 'delegate', 'bash', 'task', 'attempt_completion',
744
- 'attempt_complete', 'read_file', 'write_file', 'run_command',
745
- 'grep', 'find', 'cat', 'list_directory'
746
- ];
747
-
748
- // Look for XML tags that match known tool patterns
749
- // Only consider a tag as a tool call if:
750
- // 1. It's not in the validTools list (wouldn't have been parsed)
751
- // 2. It appears as a top-level tag (not nested inside another tool)
752
- for (const toolName of knownToolNames) {
753
- if (validTools.includes(toolName)) {
754
- continue; // Skip valid tools - these would have been parsed
755
- }
756
-
757
- const openTag = `<${toolName}>`;
758
- const closeTag = `</${toolName}>`;
759
-
760
- // Check if this tool tag exists
761
- const openIndex = xmlString.indexOf(openTag);
762
- if (openIndex === -1) {
763
- continue;
764
- }
765
-
766
- // Check if this tag is nested inside a valid tool tag
767
- let isNested = false;
768
- for (const validTool of validTools) {
769
- const validOpenTag = `<${validTool}>`;
770
- const validCloseTag = `</${validTool}>`;
771
- const validOpenIndex = xmlString.indexOf(validOpenTag);
772
- const validCloseIndex = xmlString.indexOf(validCloseTag);
773
-
774
- // If the unrecognized tool tag is between a valid tool's open and close tags, it's nested (a parameter)
775
- if (validOpenIndex !== -1 && validCloseIndex !== -1 &&
776
- validOpenIndex < openIndex && openIndex < validCloseIndex) {
777
- isNested = true;
778
- break;
779
- }
780
- }
781
-
782
- if (!isNested) {
783
- return toolName;
784
- }
785
- }
786
-
787
- // Check if any valid tool name appears inside specific wrapper patterns
788
- // This catches cases where AI wraps tools in arbitrary tags like:
789
- // <api_call><tool_name>attempt_completion</tool_name>...</api_call>
790
- // <function>search</function>
791
- // <call name="extract">...</call>
792
- // Only match specific wrapper patterns to avoid false positives with normal text
793
- const allToolNames = [...new Set([...knownToolNames, ...validTools])];
794
- for (const toolName of allToolNames) {
795
- // Escape regex metacharacters in tool name to prevent regex errors
796
- const escapedToolName = toolName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
797
-
798
- // Match specific wrapper patterns that indicate a tool call attempt:
799
- // 1. <tool_name>toolName</tool_name> - common Claude API-style wrapper
800
- // 2. <function>toolName</function> - function call style
801
- // 3. <name>toolName</name> - generic name wrapper
802
- // 4. <call><name>toolName - partial wrapper patterns
803
- const wrapperPatterns = [
804
- new RegExp(`<tool_name>\\s*${escapedToolName}\\s*</tool_name>`, 'i'),
805
- new RegExp(`<function>\\s*${escapedToolName}\\s*</function>`, 'i'),
806
- new RegExp(`<name>\\s*${escapedToolName}\\s*</name>`, 'i'),
807
- // Also check for tool name immediately after api_call or call opening tag
808
- new RegExp(`<(?:api_call|call)[^>]*>[\\s\\S]*?<tool_name>\\s*${escapedToolName}`, 'i')
809
- ];
810
-
811
- for (const pattern of wrapperPatterns) {
812
- if (pattern.test(xmlString)) {
813
- return `wrapped_tool:${toolName}`;
814
- }
815
- }
816
- }
817
-
818
- return null;
819
- }
820
-
821
- /**
822
- * Detect if a response indicates the agent is "stuck" and cannot proceed.
823
- * This uses semantic pattern matching to catch variations of "I cannot proceed"
824
- * that would bypass exact string matching.
825
- *
826
- * @param {string} response - The assistant response to check
827
- * @returns {boolean} - True if the response indicates a stuck state
828
- */
829
- export function detectStuckResponse(response) {
830
- if (!response || typeof response !== 'string') {
831
- return false;
832
- }
833
-
834
- const lowerResponse = response.toLowerCase();
835
-
836
- // Patterns that indicate the agent is stuck
837
- // Note: Use [''] to match both straight and curly apostrophes
838
- const stuckPatterns = [
839
- // Cannot proceed patterns
840
- /\bi\s+cannot\s+proceed\b/i,
841
- /\bi\s+can['']t\s+(?:proceed|continue|move\s+forward)\b/i,
842
- /\bunable\s+to\s+(?:proceed|continue|complete)\b/i,
843
- /\bblocked\b.*\b(?:proceed|continue)\b/i,
844
- // Missing information patterns
845
- /\bneed\s+(?:the|an?)\s+\w+(?:\s+\w+)?\s+to\s+(?:proceed|continue)\b/i,
846
- /\brequire[sd]?\s+(?:the|an?)\s+\w+\b.*\bto\s+(?:proceed|continue)\b/i,
847
- /\bmissing\s+(?:required|necessary|essential)\b/i,
848
- // Deadlock/loop patterns
849
- /\bdeadlock\b/i,
850
- /\bwe\s+are\s+in\s+a\s+loop\b/i,
851
- /\bstuck\s+in\s+a\s+loop\b/i,
852
- /\bi\s+(?:have|['']ve)\s+(?:explained|stated|mentioned)\s+(?:this|the\s+situation|it)\s+(?:multiple|several)\s+times\b/i,
853
- // Cannot find/get patterns
854
- /\bi\s+(?:cannot|can['']t|could\s+not|couldn['']t)\s+(?:find|locate|get|retrieve|obtain)\s+(?:the|this|that|an?)\b/i,
855
- /\bno\s+way\s+to\s+(?:find|get|obtain|retrieve)\b/i,
856
- // Exhausted options patterns
857
- /\bi\s+(?:have|['']ve)\s+exhausted\s+(?:all|my)\s+(?:available\s+)?(?:options|methods|approaches)\b/i,
858
- /\bneither\s+of\s+these\s+methods\b/i,
859
- ];
860
-
861
- for (const pattern of stuckPatterns) {
862
- if (pattern.test(response)) {
863
- return true;
864
- }
865
- }
866
-
867
- return false;
868
- }
869
-
870
- /**
871
- * Check if two responses are semantically similar (both indicate being stuck)
872
- * This is a lightweight check that groups stuck responses together
873
- *
874
- * @param {string} response1 - First response
875
- * @param {string} response2 - Second response
876
- * @returns {boolean} - True if both responses indicate a stuck state
877
- */
878
- export function areBothStuckResponses(response1, response2) {
879
- return detectStuckResponse(response1) && detectStuckResponse(response2);
880
- }
881
189
 
882
190
  /**
883
191
  * Parse targets string into array of file specifications