@probelabs/probe 0.6.0-rc254 → 0.6.0-rc256
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.
- package/README.md +166 -3
- package/bin/binaries/probe-v0.6.0-rc256-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc256-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc256-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc256-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc256-x86_64-unknown-linux-musl.tar.gz +0 -0
- package/build/agent/ProbeAgent.d.ts +1 -1
- package/build/agent/ProbeAgent.js +39 -23
- package/build/agent/acp/tools.js +2 -1
- package/build/agent/acp/tools.test.js +2 -1
- package/build/agent/bashDefaults.js +75 -6
- package/build/agent/index.js +1752 -426
- package/build/agent/mcp/xmlBridge.js +3 -2
- package/build/agent/schemaUtils.js +127 -0
- package/build/agent/tools.js +0 -28
- package/build/delegate.js +3 -0
- package/build/index.js +2 -0
- package/build/tools/common.js +26 -8
- package/build/tools/edit.js +457 -65
- package/build/tools/fileTracker.js +318 -0
- package/build/tools/fuzzyMatch.js +271 -0
- package/build/tools/hashline.js +131 -0
- package/build/tools/lineEditHeuristics.js +138 -0
- package/build/tools/symbolEdit.js +119 -0
- package/build/tools/vercel.js +40 -9
- package/cjs/agent/ProbeAgent.cjs +1863 -528
- package/cjs/index.cjs +1891 -554
- package/index.d.ts +189 -1
- package/package.json +1 -1
- package/src/agent/ProbeAgent.d.ts +1 -1
- package/src/agent/ProbeAgent.js +39 -23
- package/src/agent/acp/tools.js +2 -1
- package/src/agent/acp/tools.test.js +2 -1
- package/src/agent/bashDefaults.js +75 -6
- package/src/agent/index.js +18 -7
- package/src/agent/mcp/xmlBridge.js +3 -2
- package/src/agent/schemaUtils.js +127 -0
- package/src/agent/tools.js +0 -28
- package/src/delegate.js +3 -0
- package/src/index.js +2 -0
- package/src/tools/common.js +26 -8
- package/src/tools/edit.js +457 -65
- package/src/tools/fileTracker.js +318 -0
- package/src/tools/fuzzyMatch.js +271 -0
- package/src/tools/hashline.js +131 -0
- package/src/tools/lineEditHeuristics.js +138 -0
- package/src/tools/symbolEdit.js +119 -0
- package/src/tools/vercel.js +40 -9
- package/bin/binaries/probe-v0.6.0-rc254-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc254-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc254-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc254-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc254-x86_64-unknown-linux-musl.tar.gz +0 -0
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { MCPClientManager } from './client.js';
|
|
7
7
|
import { loadMCPConfiguration } from './config.js';
|
|
8
8
|
import { processXmlWithThinkingAndRecovery } from '../xmlParsingUtils.js';
|
|
9
|
+
import { unescapeXmlEntities } from '../../tools/common.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Convert MCP tool to XML definition format
|
|
@@ -111,7 +112,7 @@ export function parseXmlMcpToolCall(xmlString, mcpToolNames = []) {
|
|
|
111
112
|
let match;
|
|
112
113
|
while ((match = paramPattern.exec(content)) !== null) {
|
|
113
114
|
const [, paramName, paramValue] = match;
|
|
114
|
-
params[paramName] = paramValue.trim();
|
|
115
|
+
params[paramName] = unescapeXmlEntities(paramValue.trim());
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
118
|
|
|
@@ -393,7 +394,7 @@ function parseNativeXmlTool(xmlString, toolName) {
|
|
|
393
394
|
const [, paramName, paramValue] = match;
|
|
394
395
|
// Skip if this is the params tag itself (MCP format)
|
|
395
396
|
if (paramName !== 'params') {
|
|
396
|
-
params[paramName] = paramValue.trim();
|
|
397
|
+
params[paramName] = unescapeXmlEntities(paramValue.trim());
|
|
397
398
|
}
|
|
398
399
|
}
|
|
399
400
|
|
|
@@ -603,6 +603,17 @@ export function validateJsonResponse(response, options = {}) {
|
|
|
603
603
|
}
|
|
604
604
|
}
|
|
605
605
|
|
|
606
|
+
// Quick recovery: try to extract a valid JSON prefix before returning error.
|
|
607
|
+
// This handles the common case where AI returns valid JSON followed by markdown content,
|
|
608
|
+
// e.g., "Unexpected non-whitespace character after JSON at position 477" (issue #447).
|
|
609
|
+
const prefixResult = tryExtractValidJsonPrefix(responseToValidate, { schema, debug });
|
|
610
|
+
if (prefixResult && prefixResult.isValid) {
|
|
611
|
+
if (debug) {
|
|
612
|
+
console.log(`[DEBUG] JSON validation: Recovered valid JSON prefix (${prefixResult.extracted.length} chars) from response with trailing content`);
|
|
613
|
+
}
|
|
614
|
+
return { isValid: true, parsed: prefixResult.parsed };
|
|
615
|
+
}
|
|
616
|
+
|
|
606
617
|
// Create enhanced error message with context snippet
|
|
607
618
|
let enhancedError = error.message;
|
|
608
619
|
let errorContext = null;
|
|
@@ -667,6 +678,122 @@ ${pointer} here`;
|
|
|
667
678
|
}
|
|
668
679
|
}
|
|
669
680
|
|
|
681
|
+
/**
|
|
682
|
+
* Try to extract a valid JSON prefix from a string that has trailing non-JSON content.
|
|
683
|
+
* This handles the common case where an AI returns valid JSON followed by markdown text.
|
|
684
|
+
*
|
|
685
|
+
* Uses bracket-matching to find the end of the first complete JSON object/array,
|
|
686
|
+
* then validates the prefix with JSON.parse(). Optionally validates against a schema.
|
|
687
|
+
*
|
|
688
|
+
* @param {string} response - The full response string
|
|
689
|
+
* @param {Object} [options] - Options
|
|
690
|
+
* @param {Object|string} [options.schema] - JSON schema to validate against
|
|
691
|
+
* @param {boolean} [options.debug=false] - Enable debug logging
|
|
692
|
+
* @returns {Object|null} - {isValid: true, parsed: Object, extracted: string} or null if no valid prefix found
|
|
693
|
+
*/
|
|
694
|
+
export function tryExtractValidJsonPrefix(response, options = {}) {
|
|
695
|
+
const { schema = null, debug = false } = options;
|
|
696
|
+
|
|
697
|
+
if (!response || typeof response !== 'string') {
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
const trimmed = response.trim();
|
|
702
|
+
if (trimmed.length === 0) {
|
|
703
|
+
return null;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Must start with { or [
|
|
707
|
+
const firstChar = trimmed[0];
|
|
708
|
+
if (firstChar !== '{' && firstChar !== '[') {
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// First, check if the full string already parses - no need to extract prefix
|
|
713
|
+
try {
|
|
714
|
+
JSON.parse(trimmed);
|
|
715
|
+
return null; // Full string is valid JSON, no extraction needed
|
|
716
|
+
} catch {
|
|
717
|
+
// Expected - continue with prefix extraction
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Find the end of the first complete JSON object/array using bracket matching
|
|
721
|
+
const openChar = firstChar;
|
|
722
|
+
const closeChar = openChar === '{' ? '}' : ']';
|
|
723
|
+
let depth = 0;
|
|
724
|
+
let inString = false;
|
|
725
|
+
let escapeNext = false;
|
|
726
|
+
let endPos = -1;
|
|
727
|
+
|
|
728
|
+
for (let i = 0; i < trimmed.length; i++) {
|
|
729
|
+
const char = trimmed[i];
|
|
730
|
+
|
|
731
|
+
if (escapeNext) {
|
|
732
|
+
escapeNext = false;
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
if (char === '\\' && inString) {
|
|
737
|
+
escapeNext = true;
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
if (char === '"') {
|
|
742
|
+
inString = !inString;
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (inString) {
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if (char === openChar) {
|
|
751
|
+
depth++;
|
|
752
|
+
} else if (char === closeChar) {
|
|
753
|
+
depth--;
|
|
754
|
+
if (depth === 0) {
|
|
755
|
+
endPos = i + 1;
|
|
756
|
+
break;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if (endPos <= 0 || endPos >= trimmed.length) {
|
|
762
|
+
return null; // No complete JSON found, or JSON spans the entire string
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Check that there's actual non-whitespace trailing content
|
|
766
|
+
const remainder = trimmed.substring(endPos).trim();
|
|
767
|
+
if (remainder.length === 0) {
|
|
768
|
+
return null; // Only whitespace after JSON, no trailing content issue
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// Try to parse the prefix
|
|
772
|
+
const prefix = trimmed.substring(0, endPos);
|
|
773
|
+
try {
|
|
774
|
+
const parsed = JSON.parse(prefix);
|
|
775
|
+
|
|
776
|
+
if (debug) {
|
|
777
|
+
console.log(`[DEBUG] tryExtractValidJsonPrefix: Extracted valid JSON prefix (${prefix.length} chars), stripped trailing content (${remainder.length} chars)`);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// If schema provided, validate against it
|
|
781
|
+
if (schema) {
|
|
782
|
+
const schemaValidation = validateJsonResponse(prefix, { debug, schema });
|
|
783
|
+
if (!schemaValidation.isValid) {
|
|
784
|
+
if (debug) {
|
|
785
|
+
console.log(`[DEBUG] tryExtractValidJsonPrefix: Prefix is valid JSON but fails schema validation: ${schemaValidation.error}`);
|
|
786
|
+
}
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
return { isValid: true, parsed, extracted: prefix };
|
|
792
|
+
} catch {
|
|
793
|
+
return null;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
670
797
|
/**
|
|
671
798
|
* Validate that the cleaned response is valid XML if expected
|
|
672
799
|
* @param {string} response - Cleaned response
|
package/build/agent/tools.js
CHANGED
|
@@ -87,7 +87,6 @@ export function createTools(configOptions) {
|
|
|
87
87
|
if (configOptions.allowEdit && isToolAllowed('create')) {
|
|
88
88
|
tools.createTool = createTool(configOptions);
|
|
89
89
|
}
|
|
90
|
-
|
|
91
90
|
return tools;
|
|
92
91
|
}
|
|
93
92
|
|
|
@@ -132,33 +131,6 @@ export {
|
|
|
132
131
|
parseXmlToolCall
|
|
133
132
|
};
|
|
134
133
|
|
|
135
|
-
// Define the implement tool XML definition
|
|
136
|
-
export const implementToolDefinition = `
|
|
137
|
-
## implement
|
|
138
|
-
Description: Implement a given task. Can modify files. Can be used ONLY if task explicitly stated that something requires modification or implementation.
|
|
139
|
-
|
|
140
|
-
Parameters:
|
|
141
|
-
- task: (required) The task description. Should be as detailed as possible, ideally pointing to exact files which needs be modified or created.
|
|
142
|
-
- autoCommits: (optional) Whether to enable auto-commits in aider. Default is false.
|
|
143
|
-
|
|
144
|
-
Usage Example:
|
|
145
|
-
|
|
146
|
-
<examples>
|
|
147
|
-
|
|
148
|
-
User: Can you implement a function to calculate Fibonacci numbers in main.js?
|
|
149
|
-
<implement>
|
|
150
|
-
<task>Implement a recursive function to calculate the nth Fibonacci number in main.js</task>
|
|
151
|
-
</implement>
|
|
152
|
-
|
|
153
|
-
User: Can you implement a function to calculate Fibonacci numbers in main.js with auto-commits?
|
|
154
|
-
<implement>
|
|
155
|
-
<task>Implement a recursive function to calculate the nth Fibonacci number in main.js</task>
|
|
156
|
-
<autoCommits>true</autoCommits>
|
|
157
|
-
</implement>
|
|
158
|
-
|
|
159
|
-
</examples>
|
|
160
|
-
`;
|
|
161
|
-
|
|
162
134
|
// Define the listFiles tool XML definition
|
|
163
135
|
export const listFilesToolDefinition = `
|
|
164
136
|
## listFiles
|
package/build/delegate.js
CHANGED
|
@@ -346,6 +346,7 @@ const DEFAULT_DELEGATE_TIMEOUT = parseInt(process.env.DELEGATE_TIMEOUT, 10) || 3
|
|
|
346
346
|
* @param {Object} [options.tracer=null] - Telemetry tracer instance
|
|
347
347
|
* @param {boolean} [options.enableBash=false] - Enable bash tool (inherited from parent)
|
|
348
348
|
* @param {Object} [options.bashConfig] - Bash configuration (inherited from parent)
|
|
349
|
+
* @param {boolean} [options.allowEdit=false] - Allow edit/create tools (inherited from parent)
|
|
349
350
|
* @param {string} [options.architectureFileName] - Architecture context filename to embed from repo root
|
|
350
351
|
* @param {string} [options.promptType='code-researcher'] - Prompt type for the subagent
|
|
351
352
|
* @param {Array<string>|null} [options.allowedTools] - Allowed tools for the subagent (null = default)
|
|
@@ -373,6 +374,7 @@ export async function delegate({
|
|
|
373
374
|
model = null,
|
|
374
375
|
enableBash = false,
|
|
375
376
|
bashConfig = null,
|
|
377
|
+
allowEdit = false,
|
|
376
378
|
architectureFileName = null,
|
|
377
379
|
promptType = 'code-researcher',
|
|
378
380
|
allowedTools = null,
|
|
@@ -462,6 +464,7 @@ export async function delegate({
|
|
|
462
464
|
model, // Inherit from parent
|
|
463
465
|
enableBash, // Inherit from parent
|
|
464
466
|
bashConfig, // Inherit from parent
|
|
467
|
+
allowEdit, // Inherit from parent
|
|
465
468
|
architectureFileName,
|
|
466
469
|
allowedTools,
|
|
467
470
|
disableTools,
|
package/build/index.js
CHANGED
|
@@ -51,6 +51,7 @@ import { searchTool, queryTool, extractTool, delegateTool, analyzeAllTool } from
|
|
|
51
51
|
import { createExecutePlanTool, getExecutePlanToolDefinition, createCleanupExecutePlanTool, getCleanupExecutePlanToolDefinition } from './tools/executePlan.js';
|
|
52
52
|
import { bashTool } from './tools/bash.js';
|
|
53
53
|
import { editTool, createTool } from './tools/edit.js';
|
|
54
|
+
import { FileTracker } from './tools/fileTracker.js';
|
|
54
55
|
import { ProbeAgent } from './agent/ProbeAgent.js';
|
|
55
56
|
import { SimpleTelemetry, SimpleAppTracer, initializeSimpleTelemetryFromOptions } from './agent/simpleTelemetry.js';
|
|
56
57
|
import { listFilesToolInstance, searchFilesToolInstance } from './agent/probeTool.js';
|
|
@@ -98,6 +99,7 @@ export {
|
|
|
98
99
|
bashTool,
|
|
99
100
|
editTool,
|
|
100
101
|
createTool,
|
|
102
|
+
FileTracker,
|
|
101
103
|
// Export tool instances
|
|
102
104
|
listFilesToolInstance,
|
|
103
105
|
searchFilesToolInstance,
|
package/build/tools/common.js
CHANGED
|
@@ -269,6 +269,8 @@ User: Read file inside the dependency
|
|
|
269
269
|
</extract>
|
|
270
270
|
|
|
271
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.
|
|
272
274
|
`;
|
|
273
275
|
|
|
274
276
|
export const delegateToolDefinition = `
|
|
@@ -431,7 +433,7 @@ Capabilities:
|
|
|
431
433
|
|
|
432
434
|
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.';
|
|
433
435
|
export const queryDescription = 'Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.';
|
|
434
|
-
export const extractDescription = 'Extract code blocks from files based on file paths and optional line numbers. Use this tool to see complete context after finding relevant files.';
|
|
436
|
+
export const extractDescription = 'Extract code blocks from files based on file paths and optional line numbers. Use this tool to see complete context after finding relevant files. Line numbers from output can be used with edit start_line/end_line for precise editing.';
|
|
435
437
|
export const delegateDescription = 'Automatically delegate big distinct tasks to specialized probe subagents within the agentic loop. Used by AI agents to break down complex requests into focused, parallel tasks.';
|
|
436
438
|
export const bashDescription = 'Execute bash commands for system exploration and development tasks. Secure by default with built-in allow/deny lists.';
|
|
437
439
|
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.';
|
|
@@ -450,7 +452,6 @@ export const DEFAULT_VALID_TOOLS = [
|
|
|
450
452
|
'useSkill',
|
|
451
453
|
'listFiles',
|
|
452
454
|
'searchFiles',
|
|
453
|
-
'implement',
|
|
454
455
|
'bash',
|
|
455
456
|
'task',
|
|
456
457
|
'attempt_completion'
|
|
@@ -496,9 +497,9 @@ function getValidParamsForTool(toolName) {
|
|
|
496
497
|
|
|
497
498
|
const schema = schemaMap[toolName];
|
|
498
499
|
if (!schema) {
|
|
499
|
-
// For tools without schema (listFiles, searchFiles
|
|
500
|
+
// For tools without schema (listFiles, searchFiles), return common params
|
|
500
501
|
// These are the shared params that appear across multiple tools
|
|
501
|
-
return ['path', 'directory', 'pattern', 'recursive', 'includeHidden', 'task', 'files', '
|
|
502
|
+
return ['path', 'directory', 'pattern', 'recursive', 'includeHidden', 'task', 'files', 'result'];
|
|
502
503
|
}
|
|
503
504
|
|
|
504
505
|
// For attempt_completion, it has custom validation, just return 'result'
|
|
@@ -520,6 +521,23 @@ function getValidParamsForTool(toolName) {
|
|
|
520
521
|
return [];
|
|
521
522
|
}
|
|
522
523
|
|
|
524
|
+
/**
|
|
525
|
+
* Unescape standard XML entities in a string value.
|
|
526
|
+
* Order matters: & must be decoded LAST to avoid double-decoding
|
|
527
|
+
* (e.g., &lt; should become <, not <).
|
|
528
|
+
* @param {string} str - The string to unescape
|
|
529
|
+
* @returns {string} The unescaped string
|
|
530
|
+
*/
|
|
531
|
+
export function unescapeXmlEntities(str) {
|
|
532
|
+
if (typeof str !== 'string') return str;
|
|
533
|
+
return str
|
|
534
|
+
.replace(/</g, '<')
|
|
535
|
+
.replace(/>/g, '>')
|
|
536
|
+
.replace(/"/g, '"')
|
|
537
|
+
.replace(/'/g, "'")
|
|
538
|
+
.replace(/&/g, '&');
|
|
539
|
+
}
|
|
540
|
+
|
|
523
541
|
// Simple XML parser helper - safer string-based approach
|
|
524
542
|
export function parseXmlToolCall(xmlString, validTools = DEFAULT_VALID_TOOLS) {
|
|
525
543
|
// Find the tool that appears EARLIEST in the string
|
|
@@ -608,10 +626,10 @@ export function parseXmlToolCall(xmlString, validTools = DEFAULT_VALID_TOOLS) {
|
|
|
608
626
|
paramCloseIndex = nextTagIndex;
|
|
609
627
|
}
|
|
610
628
|
|
|
611
|
-
let paramValue = innerContent.substring(
|
|
629
|
+
let paramValue = unescapeXmlEntities(innerContent.substring(
|
|
612
630
|
paramOpenIndex + paramOpenTag.length,
|
|
613
631
|
paramCloseIndex
|
|
614
|
-
).trim();
|
|
632
|
+
).trim());
|
|
615
633
|
|
|
616
634
|
// Basic type inference (can be improved)
|
|
617
635
|
if (paramValue.toLowerCase() === 'true') {
|
|
@@ -632,7 +650,7 @@ export function parseXmlToolCall(xmlString, validTools = DEFAULT_VALID_TOOLS) {
|
|
|
632
650
|
|
|
633
651
|
// Special handling for attempt_completion - use entire inner content as result
|
|
634
652
|
if (toolName === 'attempt_completion') {
|
|
635
|
-
params['result'] = innerContent.trim();
|
|
653
|
+
params['result'] = unescapeXmlEntities(innerContent.trim());
|
|
636
654
|
// Remove command parameter if it was parsed by generic logic above (legacy compatibility)
|
|
637
655
|
if (params.command) {
|
|
638
656
|
delete params.command;
|
|
@@ -688,7 +706,7 @@ export function detectUnrecognizedToolCall(xmlString, validTools) {
|
|
|
688
706
|
// Common tool names that AI might try to use (these should appear as top-level tags)
|
|
689
707
|
const knownToolNames = [
|
|
690
708
|
'search', 'query', 'extract', 'listFiles', 'searchFiles',
|
|
691
|
-
'listSkills', 'useSkill', 'readImage', '
|
|
709
|
+
'listSkills', 'useSkill', 'readImage', 'edit',
|
|
692
710
|
'create', 'delegate', 'bash', 'task', 'attempt_completion',
|
|
693
711
|
'attempt_complete', 'read_file', 'write_file', 'run_command',
|
|
694
712
|
'grep', 'find', 'cat', 'list_directory'
|