@probelabs/probe 0.6.0-rc203 → 0.6.0-rc205

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 (44) hide show
  1. package/bin/binaries/probe-v0.6.0-rc205-aarch64-apple-darwin.tar.gz +0 -0
  2. package/bin/binaries/probe-v0.6.0-rc205-aarch64-unknown-linux-musl.tar.gz +0 -0
  3. package/bin/binaries/probe-v0.6.0-rc205-x86_64-apple-darwin.tar.gz +0 -0
  4. package/bin/binaries/probe-v0.6.0-rc205-x86_64-pc-windows-msvc.zip +0 -0
  5. package/bin/binaries/probe-v0.6.0-rc205-x86_64-unknown-linux-musl.tar.gz +0 -0
  6. package/build/agent/ProbeAgent.d.ts +2 -0
  7. package/build/agent/ProbeAgent.js +233 -40
  8. package/build/agent/index.js +1566 -84
  9. package/build/agent/simpleTelemetry.js +12 -0
  10. package/build/agent/tasks/TaskManager.js +604 -0
  11. package/build/agent/tasks/index.js +15 -0
  12. package/build/agent/tasks/taskTool.js +476 -0
  13. package/build/agent/tools.js +11 -0
  14. package/build/delegate.js +7 -2
  15. package/build/index.js +14 -1
  16. package/build/search.js +19 -5
  17. package/build/tools/common.js +67 -0
  18. package/build/tools/vercel.js +28 -12
  19. package/build/utils/error-types.js +303 -0
  20. package/build/utils/path-validation.js +21 -3
  21. package/cjs/agent/ProbeAgent.cjs +8940 -6393
  22. package/cjs/agent/simpleTelemetry.cjs +10 -0
  23. package/cjs/index.cjs +8960 -6393
  24. package/package.json +2 -2
  25. package/src/agent/ProbeAgent.d.ts +2 -0
  26. package/src/agent/ProbeAgent.js +233 -40
  27. package/src/agent/index.js +14 -2
  28. package/src/agent/simpleTelemetry.js +12 -0
  29. package/src/agent/tasks/TaskManager.js +604 -0
  30. package/src/agent/tasks/index.js +15 -0
  31. package/src/agent/tasks/taskTool.js +476 -0
  32. package/src/agent/tools.js +11 -0
  33. package/src/delegate.js +7 -2
  34. package/src/index.js +14 -1
  35. package/src/search.js +19 -5
  36. package/src/tools/common.js +67 -0
  37. package/src/tools/vercel.js +28 -12
  38. package/src/utils/error-types.js +303 -0
  39. package/src/utils/path-validation.js +21 -3
  40. package/bin/binaries/probe-v0.6.0-rc203-aarch64-apple-darwin.tar.gz +0 -0
  41. package/bin/binaries/probe-v0.6.0-rc203-aarch64-unknown-linux-musl.tar.gz +0 -0
  42. package/bin/binaries/probe-v0.6.0-rc203-x86_64-apple-darwin.tar.gz +0 -0
  43. package/bin/binaries/probe-v0.6.0-rc203-x86_64-pc-windows-msvc.zip +0 -0
  44. package/bin/binaries/probe-v0.6.0-rc203-x86_64-unknown-linux-musl.tar.gz +0 -0
@@ -9,6 +9,7 @@ import { query } from '../query.js';
9
9
  import { extract } from '../extract.js';
10
10
  import { delegate } from '../delegate.js';
11
11
  import { searchSchema, querySchema, extractSchema, delegateSchema, searchDescription, queryDescription, extractDescription, delegateDescription, parseTargets, parseAndResolvePaths, resolveTargetPath } from './common.js';
12
+ import { formatErrorForAI } from '../utils/error-types.js';
12
13
 
13
14
  const CODE_SEARCH_SCHEMA = {
14
15
  type: 'object',
@@ -117,15 +118,29 @@ function parseDelegatedTargets(rawResponse) {
117
118
 
118
119
  function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, allowTests }) {
119
120
  return [
120
- 'You are a code-search subagent. Your ONLY job is to return ALL relevant code locations.',
121
- 'Use ONLY the search tool. Do NOT answer the question or explain anything.',
122
- 'Return ONLY valid JSON with this shape: {"targets": ["path/to/file.ext#Symbol", "path/to/file.ext:line", "path/to/file.ext:start-end"]}.',
123
- 'Prefer #Symbol when a function/class name is clear; otherwise use line numbers.',
124
- `Search query: ${searchQuery}`,
121
+ 'You are a code-search subagent. Your job is to find ALL relevant code locations for the given query.',
122
+ '',
123
+ 'The query may be complex - it could be a natural language question, a multi-part request, or a simple keyword.',
124
+ 'Break down complex queries into multiple searches to cover all aspects.',
125
+ '',
126
+ 'Available tools:',
127
+ '- search: Find code matching keywords or patterns. Run multiple searches for different aspects of complex queries.',
128
+ '- extract: Verify code snippets to ensure targets are actually relevant before including them.',
129
+ '- listFiles: Understand directory structure to find where relevant code might live.',
130
+ '',
131
+ 'Strategy for complex queries:',
132
+ '1. Analyze the query - identify key concepts, entities, and relationships',
133
+ '2. Run focused searches for each concept (e.g., "error handling" + "authentication" separately)',
134
+ '3. Use extract to verify relevance of promising results',
135
+ '4. Combine all relevant targets in your final response',
136
+ '',
137
+ `Query: ${searchQuery}`,
125
138
  `Search path(s): ${searchPath}`,
126
139
  `Options: exact=${exact ? 'true' : 'false'}, language=${language || 'auto'}, allow_tests=${allowTests ? 'true' : 'false'}.`,
127
- 'Run additional searches only if needed to capture all relevant locations.',
128
- 'Deduplicate targets.'
140
+ '',
141
+ 'Return ONLY valid JSON: {"targets": ["path/to/file.ext#Symbol", "path/to/file.ext:line", "path/to/file.ext:start-end"]}',
142
+ 'Prefer #Symbol when a function/class name is clear; otherwise use line numbers.',
143
+ 'Deduplicate targets. Do NOT explain or answer - ONLY return the JSON targets.'
129
144
  ].join('\n');
130
145
  }
131
146
 
@@ -200,7 +215,7 @@ export const searchTool = (options = {}) => {
200
215
  return await runRawSearch();
201
216
  } catch (error) {
202
217
  console.error('Error executing search command:', error);
203
- return `Error executing search command: ${error.message}`;
218
+ return formatErrorForAI(error);
204
219
  }
205
220
  }
206
221
 
@@ -230,7 +245,7 @@ export const searchTool = (options = {}) => {
230
245
  bashConfig: null,
231
246
  architectureFileName: options.architectureFileName || null,
232
247
  promptType: 'code-searcher',
233
- allowedTools: ['search', 'attempt_completion'],
248
+ allowedTools: ['search', 'extract', 'listFiles', 'attempt_completion'],
234
249
  searchDelegate: false,
235
250
  schema: CODE_SEARCH_SCHEMA
236
251
  });
@@ -269,7 +284,8 @@ export const searchTool = (options = {}) => {
269
284
  return await runRawSearch();
270
285
  } catch (fallbackError) {
271
286
  console.error('Error executing search command:', fallbackError);
272
- return `Error executing search command: ${fallbackError.message}`;
287
+ // Both delegation and fallback failed - provide detailed error
288
+ return formatErrorForAI(fallbackError);
273
289
  }
274
290
  }
275
291
  }
@@ -322,7 +338,7 @@ export const queryTool = (options = {}) => {
322
338
  return results;
323
339
  } catch (error) {
324
340
  console.error('Error executing query command:', error);
325
- return `Error executing query command: ${error.message}`;
341
+ return formatErrorForAI(error);
326
342
  }
327
343
  }
328
344
  });
@@ -433,7 +449,7 @@ export const extractTool = (options = {}) => {
433
449
  return results;
434
450
  } catch (error) {
435
451
  console.error('Error executing extract command:', error);
436
- return `Error executing extract command: ${error.message}`;
452
+ return formatErrorForAI(error);
437
453
  }
438
454
  }
439
455
  });
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Structured error types for AI-friendly error handling
3
+ * @module utils/error-types
4
+ */
5
+
6
+ /**
7
+ * Error categories for classification
8
+ */
9
+ export const ErrorCategory = {
10
+ PATH_ERROR: 'path_error', // Path doesn't exist, not a directory, permission denied
11
+ PARAMETER_ERROR: 'parameter_error', // Invalid parameters provided
12
+ TIMEOUT_ERROR: 'timeout_error', // Operation timed out
13
+ API_ERROR: 'api_error', // AI provider errors (rate limit, token limit)
14
+ DELEGATION_ERROR: 'delegation_error', // Subagent failures
15
+ INTERNAL_ERROR: 'internal_error' // Unexpected system errors
16
+ };
17
+
18
+ /**
19
+ * Escape special characters for XML output
20
+ * @param {string} str - String to escape
21
+ * @returns {string} - Escaped string
22
+ */
23
+ function escapeXml(str) {
24
+ return String(str)
25
+ .replace(/&/g, '&')
26
+ .replace(/</g, '&lt;')
27
+ .replace(/>/g, '&gt;')
28
+ .replace(/"/g, '&quot;')
29
+ .replace(/'/g, '&apos;');
30
+ }
31
+
32
+ /**
33
+ * Base class for structured errors with AI-friendly information
34
+ */
35
+ export class ProbeError extends Error {
36
+ /**
37
+ * Create a ProbeError
38
+ * @param {string} message - Error message
39
+ * @param {Object} options - Options
40
+ * @param {string} [options.category] - Error category from ErrorCategory
41
+ * @param {boolean} [options.recoverable] - Whether the error is recoverable by AI action
42
+ * @param {string} [options.suggestion] - Suggested action for recovery
43
+ * @param {Object} [options.details] - Additional error details
44
+ * @param {Error} [options.originalError] - Original error that was wrapped
45
+ */
46
+ constructor(message, options = {}) {
47
+ super(message);
48
+ this.name = 'ProbeError';
49
+ this.category = options.category || ErrorCategory.INTERNAL_ERROR;
50
+ this.recoverable = options.recoverable ?? false;
51
+ this.suggestion = options.suggestion || null;
52
+ this.details = options.details || {};
53
+ this.originalError = options.originalError || null;
54
+ }
55
+
56
+ /**
57
+ * Format error as XML for AI consumption
58
+ * @returns {string} - XML-formatted error
59
+ */
60
+ toXml() {
61
+ const parts = [
62
+ `<error type="${this.category}" recoverable="${this.recoverable}">`,
63
+ `<message>${escapeXml(this.message)}</message>`
64
+ ];
65
+
66
+ if (this.suggestion) {
67
+ parts.push(`<suggestion>${escapeXml(this.suggestion)}</suggestion>`);
68
+ }
69
+
70
+ if (Object.keys(this.details).length > 0) {
71
+ parts.push(`<details>${escapeXml(JSON.stringify(this.details))}</details>`);
72
+ }
73
+
74
+ parts.push('</error>');
75
+ return parts.join('\n');
76
+ }
77
+
78
+ /**
79
+ * Format error for plain text (backward compatible)
80
+ * @returns {string} - Plain text error
81
+ */
82
+ toString() {
83
+ return `Error: ${this.message}`;
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Path-related errors (not found, not a directory, permission denied)
89
+ */
90
+ export class PathError extends ProbeError {
91
+ constructor(message, options = {}) {
92
+ super(message, {
93
+ ...options,
94
+ category: ErrorCategory.PATH_ERROR,
95
+ recoverable: options.recoverable ?? true,
96
+ suggestion: options.suggestion || 'Please verify the path exists or try searching in a different directory.'
97
+ });
98
+ this.name = 'PathError';
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Parameter validation errors
104
+ */
105
+ export class ParameterError extends ProbeError {
106
+ constructor(message, options = {}) {
107
+ super(message, {
108
+ ...options,
109
+ category: ErrorCategory.PARAMETER_ERROR,
110
+ recoverable: options.recoverable ?? true,
111
+ suggestion: options.suggestion || 'Please check and correct the parameter values.'
112
+ });
113
+ this.name = 'ParameterError';
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Timeout errors
119
+ */
120
+ export class TimeoutError extends ProbeError {
121
+ constructor(message, options = {}) {
122
+ super(message, {
123
+ ...options,
124
+ category: ErrorCategory.TIMEOUT_ERROR,
125
+ recoverable: options.recoverable ?? true,
126
+ suggestion: options.suggestion || 'The operation timed out. Try a more specific query or increase the timeout.'
127
+ });
128
+ this.name = 'TimeoutError';
129
+ }
130
+ }
131
+
132
+ /**
133
+ * API/AI provider errors (rate limit, token limit, etc.)
134
+ */
135
+ export class ApiError extends ProbeError {
136
+ constructor(message, options = {}) {
137
+ super(message, {
138
+ ...options,
139
+ category: ErrorCategory.API_ERROR,
140
+ recoverable: options.recoverable ?? false,
141
+ suggestion: options.suggestion || 'This is an API provider error. The system will retry automatically if possible.'
142
+ });
143
+ this.name = 'ApiError';
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Delegation/subagent errors
149
+ */
150
+ export class DelegationError extends ProbeError {
151
+ constructor(message, options = {}) {
152
+ super(message, {
153
+ ...options,
154
+ category: ErrorCategory.DELEGATION_ERROR,
155
+ recoverable: options.recoverable ?? true,
156
+ suggestion: options.suggestion || 'The delegated task failed. Consider breaking down the task further or trying a different approach.'
157
+ });
158
+ this.name = 'DelegationError';
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Categorize any error into a structured ProbeError
164
+ * @param {Error|string} error - Error to categorize
165
+ * @returns {ProbeError} - Structured error with category and suggestions
166
+ */
167
+ export function categorizeError(error) {
168
+ // If already a ProbeError, return as-is
169
+ if (error instanceof ProbeError) {
170
+ return error;
171
+ }
172
+
173
+ const message = error?.message || String(error);
174
+ const lowerMessage = message.toLowerCase();
175
+ // error.code could be a string (like 'ENOENT') or a number (like 429)
176
+ const errorCode = error?.code != null ? String(error.code).toLowerCase() : '';
177
+
178
+ // Path-related errors
179
+ if (lowerMessage.includes('path does not exist') ||
180
+ lowerMessage.includes('no such file or directory') ||
181
+ errorCode === 'enoent') {
182
+ return new PathError(message, {
183
+ originalError: error,
184
+ suggestion: 'The specified path does not exist. Please verify the path or use a different directory.'
185
+ });
186
+ }
187
+
188
+ if (lowerMessage.includes('not a directory') || errorCode === 'enotdir') {
189
+ return new PathError(message, {
190
+ originalError: error,
191
+ suggestion: 'The path is not a directory. Please provide a valid directory path.'
192
+ });
193
+ }
194
+
195
+ if (lowerMessage.includes('permission denied') || errorCode === 'eacces') {
196
+ return new PathError(message, {
197
+ originalError: error,
198
+ recoverable: false,
199
+ suggestion: 'Permission denied. This is a system-level restriction that cannot be resolved by changing the query.'
200
+ });
201
+ }
202
+
203
+ // Timeout errors
204
+ if (lowerMessage.includes('timed out') ||
205
+ lowerMessage.includes('timeout') ||
206
+ errorCode === 'etimedout' ||
207
+ error?.killed === true) {
208
+ return new TimeoutError(message, {
209
+ originalError: error,
210
+ suggestion: 'The operation timed out. Try a more specific query, reduce the search scope, or increase the timeout.'
211
+ });
212
+ }
213
+
214
+ // API errors - rate limiting and overload
215
+ const rateLimitPatterns = ['rate_limit', 'rate limit', '429', 'too many requests', 'overloaded'];
216
+ if (rateLimitPatterns.some(p => lowerMessage.includes(p))) {
217
+ return new ApiError(message, {
218
+ originalError: error,
219
+ recoverable: true,
220
+ suggestion: 'API rate limit or overload encountered. The system will retry automatically with backoff.'
221
+ });
222
+ }
223
+
224
+ // API errors - server errors
225
+ const serverErrorPatterns = ['500', '502', '503', '504', 'internal server error', 'bad gateway', 'service unavailable'];
226
+ if (serverErrorPatterns.some(p => lowerMessage.includes(p))) {
227
+ return new ApiError(message, {
228
+ originalError: error,
229
+ recoverable: true,
230
+ suggestion: 'API server error encountered. The system will retry automatically.'
231
+ });
232
+ }
233
+
234
+ // API errors - context/token limits
235
+ if ((lowerMessage.includes('context') || lowerMessage.includes('token')) &&
236
+ (lowerMessage.includes('limit') || lowerMessage.includes('exceed'))) {
237
+ return new ApiError(message, {
238
+ originalError: error,
239
+ recoverable: true,
240
+ suggestion: 'Context or token limit exceeded. The conversation may be automatically compacted.'
241
+ });
242
+ }
243
+
244
+ // Parameter validation errors (check before API auth to catch 'invalid parameter' first)
245
+ if (lowerMessage.includes('invalid parameter') ||
246
+ lowerMessage.includes('missing parameter') ||
247
+ lowerMessage.includes('required') ||
248
+ lowerMessage.includes('must be')) {
249
+ return new ParameterError(message, {
250
+ originalError: error
251
+ });
252
+ }
253
+
254
+ // API errors - authentication (after parameter errors to avoid catching 'invalid parameter')
255
+ if (lowerMessage.includes('invalid') && (lowerMessage.includes('api') || lowerMessage.includes('key') || lowerMessage.includes('auth'))) {
256
+ return new ApiError(message, {
257
+ originalError: error,
258
+ recoverable: false,
259
+ suggestion: 'API authentication error. Please check the API key configuration.'
260
+ });
261
+ }
262
+
263
+ // Delegation errors
264
+ if (lowerMessage.includes('delegation failed') ||
265
+ lowerMessage.includes('delegate agent') ||
266
+ lowerMessage.includes('subagent')) {
267
+ return new DelegationError(message, {
268
+ originalError: error
269
+ });
270
+ }
271
+
272
+ // Network errors
273
+ if (errorCode === 'econnreset' ||
274
+ errorCode === 'econnrefused' ||
275
+ errorCode === 'enotfound' ||
276
+ lowerMessage.includes('network') ||
277
+ lowerMessage.includes('connection')) {
278
+ return new ApiError(message, {
279
+ originalError: error,
280
+ recoverable: true,
281
+ suggestion: 'Network error encountered. The system will retry automatically.'
282
+ });
283
+ }
284
+
285
+ // Default: internal error
286
+ return new ProbeError(message, {
287
+ category: ErrorCategory.INTERNAL_ERROR,
288
+ recoverable: false,
289
+ originalError: error,
290
+ suggestion: 'An unexpected error occurred. Please check the error message for details.'
291
+ });
292
+ }
293
+
294
+ /**
295
+ * Format an error for AI consumption as XML
296
+ * Works with both ProbeError and regular Error objects
297
+ * @param {Error} error - Error to format
298
+ * @returns {string} - XML-formatted error string
299
+ */
300
+ export function formatErrorForAI(error) {
301
+ const structuredError = categorizeError(error);
302
+ return structuredError.toXml();
303
+ }
@@ -5,6 +5,7 @@
5
5
 
6
6
  import path from 'path';
7
7
  import { promises as fs } from 'fs';
8
+ import { PathError } from './error-types.js';
8
9
 
9
10
  /**
10
11
  * Validates and normalizes a path to be used as working directory (cwd).
@@ -18,7 +19,7 @@ import { promises as fs } from 'fs';
18
19
  * @param {string} inputPath - The path to validate
19
20
  * @param {string} [defaultPath] - Default path to use if inputPath is not provided
20
21
  * @returns {Promise<string>} Normalized absolute path
21
- * @throws {Error} If the path is invalid or doesn't exist
22
+ * @throws {PathError} If the path is invalid or doesn't exist
22
23
  */
23
24
  export async function validateCwdPath(inputPath, defaultPath = process.cwd()) {
24
25
  // Use default if not provided
@@ -32,11 +33,28 @@ export async function validateCwdPath(inputPath, defaultPath = process.cwd()) {
32
33
  try {
33
34
  const stats = await fs.stat(normalizedPath);
34
35
  if (!stats.isDirectory()) {
35
- throw new Error(`Path is not a directory: ${normalizedPath}`);
36
+ throw new PathError(`Path is not a directory: ${normalizedPath}`, {
37
+ suggestion: 'The specified path is a file, not a directory. Please provide a directory path for searching.',
38
+ details: { path: normalizedPath, type: 'file' }
39
+ });
36
40
  }
37
41
  } catch (error) {
42
+ // Re-throw if already a PathError
43
+ if (error instanceof PathError) {
44
+ throw error;
45
+ }
38
46
  if (error.code === 'ENOENT') {
39
- throw new Error(`Path does not exist: ${normalizedPath}`);
47
+ throw new PathError(`Path does not exist: ${normalizedPath}`, {
48
+ suggestion: 'The specified path does not exist. Please verify the path is correct or use a different directory.',
49
+ details: { path: normalizedPath }
50
+ });
51
+ }
52
+ if (error.code === 'EACCES') {
53
+ throw new PathError(`Permission denied: ${normalizedPath}`, {
54
+ recoverable: false,
55
+ suggestion: 'Permission denied accessing this path. This is a system-level restriction.',
56
+ details: { path: normalizedPath }
57
+ });
40
58
  }
41
59
  throw error;
42
60
  }