@probelabs/probe 0.6.0-rc288 → 0.6.0-rc290
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/bin/binaries/probe-v0.6.0-rc290-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc290-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc290-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc290-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc290-x86_64-unknown-linux-musl.tar.gz +0 -0
- package/build/agent/ProbeAgent.js +61 -10
- package/build/agent/index.js +401 -86261
- package/build/agent/shared/prompts.js +27 -6
- package/build/extract.js +4 -2
- package/build/mcp/index.js +122 -9
- package/build/mcp/index.ts +162 -17
- package/build/search.js +6 -5
- package/build/tools/vercel.js +51 -22
- package/cjs/agent/ProbeAgent.cjs +131 -38
- package/cjs/index.cjs +131 -38
- package/package.json +2 -1
- package/src/agent/ProbeAgent.js +61 -10
- package/src/agent/shared/prompts.js +27 -6
- package/src/extract.js +4 -2
- package/src/mcp/index.ts +162 -17
- package/src/search.js +6 -5
- package/src/tools/vercel.js +51 -22
- package/bin/binaries/probe-v0.6.0-rc288-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc288-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc288-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc288-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc288-x86_64-unknown-linux-musl.tar.gz +0 -0
package/build/tools/vercel.js
CHANGED
|
@@ -69,12 +69,12 @@ function autoQuoteSearchTerms(query) {
|
|
|
69
69
|
if (token.startsWith('"')) return token;
|
|
70
70
|
// Boolean operator
|
|
71
71
|
if (operators.has(token)) return token;
|
|
72
|
-
// Check if token needs quoting: has
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
// Check if token needs quoting: has camelCase/PascalCase transitions or underscores
|
|
73
|
+
// Simple capitalized words like "Redis" or "Limiter" should NOT be quoted —
|
|
74
|
+
// only quote when there's an actual case transition (e.g., "getUserData", "NewSlidingLog")
|
|
75
75
|
const hasUnderscore = token.includes('_');
|
|
76
|
-
const
|
|
77
|
-
if (
|
|
76
|
+
const hasCaseTransition = /[a-z][A-Z]/.test(token) || /[A-Z]{2,}[a-z]/.test(token);
|
|
77
|
+
if (hasCaseTransition || hasUnderscore) {
|
|
78
78
|
return `"${token}"`;
|
|
79
79
|
}
|
|
80
80
|
return token;
|
|
@@ -237,7 +237,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
237
237
|
'Break down complex queries into multiple searches to cover all aspects.',
|
|
238
238
|
'',
|
|
239
239
|
'Available tools:',
|
|
240
|
-
'- search: Find code matching keywords or patterns. Run multiple searches for different aspects
|
|
240
|
+
'- search: Find code matching keywords or patterns. Results are paginated — use nextPage=true when results are relevant to get more. Run multiple searches for different aspects.',
|
|
241
241
|
'- extract: Verify code snippets to ensure targets are actually relevant before including them.',
|
|
242
242
|
'- listFiles: Understand directory structure to find where relevant code might live.',
|
|
243
243
|
'',
|
|
@@ -258,13 +258,14 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
258
258
|
'',
|
|
259
259
|
'Combining searches with OR:',
|
|
260
260
|
'- Multiple unquoted words use OR logic: rate limit matches files containing EITHER "rate" OR "limit".',
|
|
261
|
-
'-
|
|
261
|
+
'- IMPORTANT: Multiple quoted terms use AND logic by default: \'"RateLimit" "middleware"\' requires BOTH in the same file.',
|
|
262
|
+
'- To search for ANY of several quoted symbols, use the explicit OR operator: \'"ForwardMessage" OR "SessionLimiter"\'.',
|
|
262
263
|
'- Without quotes, camelCase like limitDRL gets split into "limit" + "DRL" — not what you want for symbol lookup.',
|
|
263
264
|
'- Use OR to search for multiple related symbols in ONE search instead of separate searches.',
|
|
264
265
|
'- This is much faster than running separate searches sequentially.',
|
|
265
|
-
'- Example: search \'"ForwardMessage" "SessionLimiter"\' finds files with either exact symbol in one call.',
|
|
266
|
-
'- Example: search \'"limitDRL" "doRollingWindowWrite"\' finds both rate limiting functions at once.',
|
|
267
|
-
'- Use AND
|
|
266
|
+
'- Example: search \'"ForwardMessage" OR "SessionLimiter"\' finds files with either exact symbol in one call.',
|
|
267
|
+
'- Example: search \'"limitDRL" OR "doRollingWindowWrite"\' finds both rate limiting functions at once.',
|
|
268
|
+
'- Use AND (or just put quoted terms together) when you need both terms in the same file.',
|
|
268
269
|
'',
|
|
269
270
|
'Parallel tool calls:',
|
|
270
271
|
'- When you need to search for INDEPENDENT concepts, call multiple search tools IN PARALLEL (same response).',
|
|
@@ -278,10 +279,10 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
278
279
|
' Query: "Find the IP allowlist middleware"',
|
|
279
280
|
' → search "allowlist middleware" (one search, probe handles IP/ip/Ip variations)',
|
|
280
281
|
' Query: "Find ForwardMessage and SessionLimiter"',
|
|
281
|
-
' → search \'"ForwardMessage" "SessionLimiter"\' (one OR search finds both exact symbols)',
|
|
282
|
+
' → search \'"ForwardMessage" OR "SessionLimiter"\' (one OR search finds both exact symbols)',
|
|
282
283
|
' OR: search exact=true "ForwardMessage" + search exact=true "SessionLimiter" IN PARALLEL',
|
|
283
284
|
' Query: "Find limitDRL and limitRedis functions"',
|
|
284
|
-
' → search \'"limitDRL" "limitRedis"\' (one OR search, quoted to prevent camelCase splitting)',
|
|
285
|
+
' → search \'"limitDRL" OR "limitRedis"\' (one OR search, quoted to prevent camelCase splitting)',
|
|
285
286
|
' Query: "Find ThrottleRetryLimit usage"',
|
|
286
287
|
' → search exact=true "ThrottleRetryLimit" (one search, if no results the symbol does not exist — stop)',
|
|
287
288
|
' Query: "How does BM25 scoring work with SIMD optimization?"',
|
|
@@ -289,7 +290,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
289
290
|
'',
|
|
290
291
|
'BAD search strategy (never do this):',
|
|
291
292
|
' → search "AllowedIPs" → search "allowedIps" → search "allowed_ips" (WRONG: case/style variations, probe handles them)',
|
|
292
|
-
' → search "limitDRL" → search "LimitDRL" (WRONG: case variation — combine with OR: \'"limitDRL" "limitRedis"\')',
|
|
293
|
+
' → search "limitDRL" → search "LimitDRL" (WRONG: case variation — combine with OR: \'"limitDRL" OR "limitRedis"\')',
|
|
293
294
|
' → search "throttle_retry_limit" after searching "ThrottleRetryLimit" (WRONG: snake_case variation, probe handles it)',
|
|
294
295
|
' → search "ThrottleRetryLimit" path=tyk → search "ThrottleRetryLimit" path=gateway → search "ThrottleRetryLimit" path=apidef (WRONG: same query on different paths — probe searches recursively)',
|
|
295
296
|
' → search "func (k *RateLimitAndQuotaCheck) handleRateLimitFailure" (WRONG: do not search full function signatures, just use exact=true "handleRateLimitFailure")',
|
|
@@ -302,15 +303,34 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
302
303
|
'- To bypass stopword filtering: wrap terms in quotes ("return", "struct") or set exact=true. Both disable stemming and splitting too.',
|
|
303
304
|
'- camelCase terms are split: getUserData becomes "get", "user", "data" — so one search covers all naming styles.',
|
|
304
305
|
'- Do NOT search for full function signatures like "func (r *Type) Method(args)". Just search for the method name with exact=true.',
|
|
306
|
+
'- Do NOT search for file names (e.g., "sliding_log.go"). Use listFiles to discover files by name.',
|
|
307
|
+
'',
|
|
308
|
+
'PAGINATION:',
|
|
309
|
+
'- Search results are paginated (~20k tokens per page).',
|
|
310
|
+
'- If your search returned relevant files, call the same query with nextPage=true to check for more.',
|
|
311
|
+
'- Keep paginating while results stay relevant. Stop when results are off-topic or "All results retrieved".',
|
|
312
|
+
'',
|
|
313
|
+
'WHEN TO STOP:',
|
|
314
|
+
'- After you have explored the main concept AND related subsystems.',
|
|
315
|
+
'- Once you have 5-15 targets covering different aspects of the query.',
|
|
316
|
+
'- If you get a "DUPLICATE SEARCH BLOCKED" message, move on.',
|
|
305
317
|
'',
|
|
306
318
|
'Strategy:',
|
|
307
|
-
'1. Analyze the query
|
|
308
|
-
'
|
|
309
|
-
'
|
|
319
|
+
'1. Analyze the query — identify key concepts, then brainstorm SYNONYMS and alternative terms for each.',
|
|
320
|
+
' Code naming often differs from the concept: "authentication" → verify, credentials, login, auth;',
|
|
321
|
+
' "rate limiting" → throttle, quota, limiter, bucket; "error handling" → catch, recover, panic.',
|
|
322
|
+
' Think about what a developer would NAME the function/struct/variable, not just the concept.',
|
|
323
|
+
'2. Run INDEPENDENT searches in PARALLEL — search for the main concept AND synonyms simultaneously.',
|
|
324
|
+
' After each search, check if results are relevant. If yes, call nextPage=true for more results.',
|
|
325
|
+
'3. Combine related symbols into OR searches: \'"symbolA" OR "symbolB"\' finds files with either.',
|
|
310
326
|
'4. For known symbol names use exact=true. For concepts use default (exact=false).',
|
|
311
|
-
'5.
|
|
312
|
-
'
|
|
313
|
-
'
|
|
327
|
+
'5. After your first round of searches, READ the extracted code and look for connected code:',
|
|
328
|
+
' - Function calls to other important functions → include those targets.',
|
|
329
|
+
' - Type references and imports → include type definitions.',
|
|
330
|
+
' - Registered handlers/middleware → include all registered items.',
|
|
331
|
+
'6. If a search returns results, use extract to verify relevance. Run multiple extracts in parallel too.',
|
|
332
|
+
'7. If a search returns NO results, the term does not exist. Do NOT retry with variations. Move on.',
|
|
333
|
+
'8. Once you have enough targets (typically 5-15), output your final JSON answer immediately.',
|
|
314
334
|
'',
|
|
315
335
|
`Query: ${searchQuery}`,
|
|
316
336
|
`Search path(s): ${searchPath}`,
|
|
@@ -319,7 +339,9 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
319
339
|
'Return ONLY valid JSON: {"targets": ["path/to/file.ext#Symbol", "path/to/file.ext:line", "path/to/file.ext:start-end"]}',
|
|
320
340
|
'IMPORTANT: Use ABSOLUTE file paths in targets (e.g., "/full/path/to/file.ext#Symbol"). If you only have relative paths, make them relative to the search path above.',
|
|
321
341
|
'Prefer #Symbol when a function/class name is clear; otherwise use line numbers.',
|
|
322
|
-
'Deduplicate targets. Do NOT explain or answer - ONLY return the JSON targets.'
|
|
342
|
+
'Deduplicate targets. Do NOT explain or answer - ONLY return the JSON targets.',
|
|
343
|
+
'',
|
|
344
|
+
'Remember: if your search returned relevant results, use nextPage=true to check for more before outputting.'
|
|
323
345
|
].join('\n');
|
|
324
346
|
}
|
|
325
347
|
|
|
@@ -351,6 +373,8 @@ export const searchTool = (options = {}) => {
|
|
|
351
373
|
|
|
352
374
|
// Track previous non-paginated searches to detect and block duplicates
|
|
353
375
|
const previousSearches = new Set();
|
|
376
|
+
// Track how many times a duplicate search has been blocked (for escalating messages)
|
|
377
|
+
let consecutiveDupBlocks = 0;
|
|
354
378
|
// Track pagination counts per query to cap runaway pagination
|
|
355
379
|
const paginationCounts = new Map();
|
|
356
380
|
const MAX_PAGES_PER_QUERY = 3;
|
|
@@ -422,12 +446,17 @@ export const searchTool = (options = {}) => {
|
|
|
422
446
|
const searchKey = `${searchQuery}::${exact || false}`;
|
|
423
447
|
if (!nextPage) {
|
|
424
448
|
if (previousSearches.has(searchKey)) {
|
|
449
|
+
consecutiveDupBlocks++;
|
|
425
450
|
if (debug) {
|
|
426
|
-
console.error(`[DEDUP] Blocked duplicate search: "${searchQuery}" (path: "${searchPath}")`);
|
|
451
|
+
console.error(`[DEDUP] Blocked duplicate search (${consecutiveDupBlocks}x): "${searchQuery}" (path: "${searchPath}")`);
|
|
452
|
+
}
|
|
453
|
+
if (consecutiveDupBlocks >= 3) {
|
|
454
|
+
return 'STOP. You have been blocked ' + consecutiveDupBlocks + ' times for repeating searches. You MUST output your final JSON answer NOW with whatever targets you have found. Do NOT call any more tools.';
|
|
427
455
|
}
|
|
428
|
-
return 'DUPLICATE SEARCH BLOCKED
|
|
456
|
+
return 'DUPLICATE SEARCH BLOCKED (' + consecutiveDupBlocks + 'x). You already searched for this. Do NOT repeat — probe searches recursively across all paths. Either: (1) use extract on results you already found, (2) try a COMPLETELY different keyword, or (3) output your final answer NOW.';
|
|
429
457
|
}
|
|
430
458
|
previousSearches.add(searchKey);
|
|
459
|
+
consecutiveDupBlocks = 0; // Reset on successful new search
|
|
431
460
|
paginationCounts.set(searchKey, 0);
|
|
432
461
|
} else {
|
|
433
462
|
// Cap pagination to prevent runaway page-through of broad queries
|
package/cjs/agent/ProbeAgent.cjs
CHANGED
|
@@ -25821,7 +25821,8 @@ var init_search = __esm({
|
|
|
25821
25821
|
session: "--session",
|
|
25822
25822
|
timeout: "--timeout",
|
|
25823
25823
|
language: "--language",
|
|
25824
|
-
format: "--format"
|
|
25824
|
+
format: "--format",
|
|
25825
|
+
lsp: "--lsp"
|
|
25825
25826
|
};
|
|
25826
25827
|
}
|
|
25827
25828
|
});
|
|
@@ -26057,7 +26058,8 @@ var init_extract = __esm({
|
|
|
26057
26058
|
allowTests: "--allow-tests",
|
|
26058
26059
|
contextLines: "--context",
|
|
26059
26060
|
format: "--format",
|
|
26060
|
-
inputFile: "--input-file"
|
|
26061
|
+
inputFile: "--input-file",
|
|
26062
|
+
lsp: "--lsp"
|
|
26061
26063
|
};
|
|
26062
26064
|
}
|
|
26063
26065
|
});
|
|
@@ -27291,11 +27293,9 @@ function autoQuoteSearchTerms(query2) {
|
|
|
27291
27293
|
const result = tokens.map((token) => {
|
|
27292
27294
|
if (token.startsWith('"')) return token;
|
|
27293
27295
|
if (operators.has(token)) return token;
|
|
27294
|
-
const hasUpper = /[A-Z]/.test(token);
|
|
27295
|
-
const hasLower = /[a-z]/.test(token);
|
|
27296
27296
|
const hasUnderscore = token.includes("_");
|
|
27297
|
-
const
|
|
27298
|
-
if (
|
|
27297
|
+
const hasCaseTransition = /[a-z][A-Z]/.test(token) || /[A-Z]{2,}[a-z]/.test(token);
|
|
27298
|
+
if (hasCaseTransition || hasUnderscore) {
|
|
27299
27299
|
return `"${token}"`;
|
|
27300
27300
|
}
|
|
27301
27301
|
return token;
|
|
@@ -27410,7 +27410,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
27410
27410
|
"Break down complex queries into multiple searches to cover all aspects.",
|
|
27411
27411
|
"",
|
|
27412
27412
|
"Available tools:",
|
|
27413
|
-
"- search: Find code matching keywords or patterns. Run multiple searches for different aspects
|
|
27413
|
+
"- search: Find code matching keywords or patterns. Results are paginated \u2014 use nextPage=true when results are relevant to get more. Run multiple searches for different aspects.",
|
|
27414
27414
|
"- extract: Verify code snippets to ensure targets are actually relevant before including them.",
|
|
27415
27415
|
"- listFiles: Understand directory structure to find where relevant code might live.",
|
|
27416
27416
|
"",
|
|
@@ -27431,13 +27431,14 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
27431
27431
|
"",
|
|
27432
27432
|
"Combining searches with OR:",
|
|
27433
27433
|
'- Multiple unquoted words use OR logic: rate limit matches files containing EITHER "rate" OR "limit".',
|
|
27434
|
-
`-
|
|
27434
|
+
`- IMPORTANT: Multiple quoted terms use AND logic by default: '"RateLimit" "middleware"' requires BOTH in the same file.`,
|
|
27435
|
+
`- To search for ANY of several quoted symbols, use the explicit OR operator: '"ForwardMessage" OR "SessionLimiter"'.`,
|
|
27435
27436
|
'- Without quotes, camelCase like limitDRL gets split into "limit" + "DRL" \u2014 not what you want for symbol lookup.',
|
|
27436
27437
|
"- Use OR to search for multiple related symbols in ONE search instead of separate searches.",
|
|
27437
27438
|
"- This is much faster than running separate searches sequentially.",
|
|
27438
|
-
`- Example: search '"ForwardMessage" "SessionLimiter"' finds files with either exact symbol in one call.`,
|
|
27439
|
-
`- Example: search '"limitDRL" "doRollingWindowWrite"' finds both rate limiting functions at once.`,
|
|
27440
|
-
|
|
27439
|
+
`- Example: search '"ForwardMessage" OR "SessionLimiter"' finds files with either exact symbol in one call.`,
|
|
27440
|
+
`- Example: search '"limitDRL" OR "doRollingWindowWrite"' finds both rate limiting functions at once.`,
|
|
27441
|
+
"- Use AND (or just put quoted terms together) when you need both terms in the same file.",
|
|
27441
27442
|
"",
|
|
27442
27443
|
"Parallel tool calls:",
|
|
27443
27444
|
"- When you need to search for INDEPENDENT concepts, call multiple search tools IN PARALLEL (same response).",
|
|
@@ -27451,10 +27452,10 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
27451
27452
|
' Query: "Find the IP allowlist middleware"',
|
|
27452
27453
|
' \u2192 search "allowlist middleware" (one search, probe handles IP/ip/Ip variations)',
|
|
27453
27454
|
' Query: "Find ForwardMessage and SessionLimiter"',
|
|
27454
|
-
` \u2192 search '"ForwardMessage" "SessionLimiter"' (one OR search finds both exact symbols)`,
|
|
27455
|
+
` \u2192 search '"ForwardMessage" OR "SessionLimiter"' (one OR search finds both exact symbols)`,
|
|
27455
27456
|
' OR: search exact=true "ForwardMessage" + search exact=true "SessionLimiter" IN PARALLEL',
|
|
27456
27457
|
' Query: "Find limitDRL and limitRedis functions"',
|
|
27457
|
-
` \u2192 search '"limitDRL" "limitRedis"' (one OR search, quoted to prevent camelCase splitting)`,
|
|
27458
|
+
` \u2192 search '"limitDRL" OR "limitRedis"' (one OR search, quoted to prevent camelCase splitting)`,
|
|
27458
27459
|
' Query: "Find ThrottleRetryLimit usage"',
|
|
27459
27460
|
' \u2192 search exact=true "ThrottleRetryLimit" (one search, if no results the symbol does not exist \u2014 stop)',
|
|
27460
27461
|
' Query: "How does BM25 scoring work with SIMD optimization?"',
|
|
@@ -27462,7 +27463,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
27462
27463
|
"",
|
|
27463
27464
|
"BAD search strategy (never do this):",
|
|
27464
27465
|
' \u2192 search "AllowedIPs" \u2192 search "allowedIps" \u2192 search "allowed_ips" (WRONG: case/style variations, probe handles them)',
|
|
27465
|
-
` \u2192 search "limitDRL" \u2192 search "LimitDRL" (WRONG: case variation \u2014 combine with OR: '"limitDRL" "limitRedis"')`,
|
|
27466
|
+
` \u2192 search "limitDRL" \u2192 search "LimitDRL" (WRONG: case variation \u2014 combine with OR: '"limitDRL" OR "limitRedis"')`,
|
|
27466
27467
|
' \u2192 search "throttle_retry_limit" after searching "ThrottleRetryLimit" (WRONG: snake_case variation, probe handles it)',
|
|
27467
27468
|
' \u2192 search "ThrottleRetryLimit" path=tyk \u2192 search "ThrottleRetryLimit" path=gateway \u2192 search "ThrottleRetryLimit" path=apidef (WRONG: same query on different paths \u2014 probe searches recursively)',
|
|
27468
27469
|
' \u2192 search "func (k *RateLimitAndQuotaCheck) handleRateLimitFailure" (WRONG: do not search full function signatures, just use exact=true "handleRateLimitFailure")',
|
|
@@ -27475,15 +27476,34 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
27475
27476
|
'- To bypass stopword filtering: wrap terms in quotes ("return", "struct") or set exact=true. Both disable stemming and splitting too.',
|
|
27476
27477
|
'- camelCase terms are split: getUserData becomes "get", "user", "data" \u2014 so one search covers all naming styles.',
|
|
27477
27478
|
'- Do NOT search for full function signatures like "func (r *Type) Method(args)". Just search for the method name with exact=true.',
|
|
27479
|
+
'- Do NOT search for file names (e.g., "sliding_log.go"). Use listFiles to discover files by name.',
|
|
27480
|
+
"",
|
|
27481
|
+
"PAGINATION:",
|
|
27482
|
+
"- Search results are paginated (~20k tokens per page).",
|
|
27483
|
+
"- If your search returned relevant files, call the same query with nextPage=true to check for more.",
|
|
27484
|
+
'- Keep paginating while results stay relevant. Stop when results are off-topic or "All results retrieved".',
|
|
27485
|
+
"",
|
|
27486
|
+
"WHEN TO STOP:",
|
|
27487
|
+
"- After you have explored the main concept AND related subsystems.",
|
|
27488
|
+
"- Once you have 5-15 targets covering different aspects of the query.",
|
|
27489
|
+
'- If you get a "DUPLICATE SEARCH BLOCKED" message, move on.',
|
|
27478
27490
|
"",
|
|
27479
27491
|
"Strategy:",
|
|
27480
|
-
"1. Analyze the query
|
|
27481
|
-
|
|
27482
|
-
"
|
|
27492
|
+
"1. Analyze the query \u2014 identify key concepts, then brainstorm SYNONYMS and alternative terms for each.",
|
|
27493
|
+
' Code naming often differs from the concept: "authentication" \u2192 verify, credentials, login, auth;',
|
|
27494
|
+
' "rate limiting" \u2192 throttle, quota, limiter, bucket; "error handling" \u2192 catch, recover, panic.',
|
|
27495
|
+
" Think about what a developer would NAME the function/struct/variable, not just the concept.",
|
|
27496
|
+
"2. Run INDEPENDENT searches in PARALLEL \u2014 search for the main concept AND synonyms simultaneously.",
|
|
27497
|
+
" After each search, check if results are relevant. If yes, call nextPage=true for more results.",
|
|
27498
|
+
`3. Combine related symbols into OR searches: '"symbolA" OR "symbolB"' finds files with either.`,
|
|
27483
27499
|
"4. For known symbol names use exact=true. For concepts use default (exact=false).",
|
|
27484
|
-
"5.
|
|
27485
|
-
"
|
|
27486
|
-
"
|
|
27500
|
+
"5. After your first round of searches, READ the extracted code and look for connected code:",
|
|
27501
|
+
" - Function calls to other important functions \u2192 include those targets.",
|
|
27502
|
+
" - Type references and imports \u2192 include type definitions.",
|
|
27503
|
+
" - Registered handlers/middleware \u2192 include all registered items.",
|
|
27504
|
+
"6. If a search returns results, use extract to verify relevance. Run multiple extracts in parallel too.",
|
|
27505
|
+
"7. If a search returns NO results, the term does not exist. Do NOT retry with variations. Move on.",
|
|
27506
|
+
"8. Once you have enough targets (typically 5-15), output your final JSON answer immediately.",
|
|
27487
27507
|
"",
|
|
27488
27508
|
`Query: ${searchQuery}`,
|
|
27489
27509
|
`Search path(s): ${searchPath}`,
|
|
@@ -27492,7 +27512,9 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
27492
27512
|
'Return ONLY valid JSON: {"targets": ["path/to/file.ext#Symbol", "path/to/file.ext:line", "path/to/file.ext:start-end"]}',
|
|
27493
27513
|
'IMPORTANT: Use ABSOLUTE file paths in targets (e.g., "/full/path/to/file.ext#Symbol"). If you only have relative paths, make them relative to the search path above.',
|
|
27494
27514
|
"Prefer #Symbol when a function/class name is clear; otherwise use line numbers.",
|
|
27495
|
-
"Deduplicate targets. Do NOT explain or answer - ONLY return the JSON targets."
|
|
27515
|
+
"Deduplicate targets. Do NOT explain or answer - ONLY return the JSON targets.",
|
|
27516
|
+
"",
|
|
27517
|
+
"Remember: if your search returned relevant results, use nextPage=true to check for more before outputting."
|
|
27496
27518
|
].join("\n");
|
|
27497
27519
|
}
|
|
27498
27520
|
var import_ai, import_fs4, CODE_SEARCH_SCHEMA, searchTool, queryTool, extractTool, delegateTool, analyzeAllTool;
|
|
@@ -27537,6 +27559,7 @@ var init_vercel = __esm({
|
|
|
27537
27559
|
return result;
|
|
27538
27560
|
};
|
|
27539
27561
|
const previousSearches = /* @__PURE__ */ new Set();
|
|
27562
|
+
let consecutiveDupBlocks = 0;
|
|
27540
27563
|
const paginationCounts = /* @__PURE__ */ new Map();
|
|
27541
27564
|
const MAX_PAGES_PER_QUERY = 3;
|
|
27542
27565
|
return (0, import_ai.tool)({
|
|
@@ -27589,12 +27612,17 @@ var init_vercel = __esm({
|
|
|
27589
27612
|
const searchKey = `${searchQuery}::${exact || false}`;
|
|
27590
27613
|
if (!nextPage) {
|
|
27591
27614
|
if (previousSearches.has(searchKey)) {
|
|
27615
|
+
consecutiveDupBlocks++;
|
|
27592
27616
|
if (debug) {
|
|
27593
|
-
console.error(`[DEDUP] Blocked duplicate search: "${searchQuery}" (path: "${searchPath}")`);
|
|
27617
|
+
console.error(`[DEDUP] Blocked duplicate search (${consecutiveDupBlocks}x): "${searchQuery}" (path: "${searchPath}")`);
|
|
27594
27618
|
}
|
|
27595
|
-
|
|
27619
|
+
if (consecutiveDupBlocks >= 3) {
|
|
27620
|
+
return "STOP. You have been blocked " + consecutiveDupBlocks + " times for repeating searches. You MUST output your final JSON answer NOW with whatever targets you have found. Do NOT call any more tools.";
|
|
27621
|
+
}
|
|
27622
|
+
return "DUPLICATE SEARCH BLOCKED (" + consecutiveDupBlocks + "x). You already searched for this. Do NOT repeat \u2014 probe searches recursively across all paths. Either: (1) use extract on results you already found, (2) try a COMPLETELY different keyword, or (3) output your final answer NOW.";
|
|
27596
27623
|
}
|
|
27597
27624
|
previousSearches.add(searchKey);
|
|
27625
|
+
consecutiveDupBlocks = 0;
|
|
27598
27626
|
paginationCounts.set(searchKey, 0);
|
|
27599
27627
|
} else {
|
|
27600
27628
|
const pageCount = (paginationCounts.get(searchKey) || 0) + 1;
|
|
@@ -88375,26 +88403,46 @@ var init_prompts = __esm({
|
|
|
88375
88403
|
CRITICAL - You are READ-ONLY:
|
|
88376
88404
|
You must NEVER create, modify, delete, or write files. You are strictly an exploration and analysis tool. If asked to make changes, implement features, fix bugs, or modify a PR, refuse and explain that file modifications must be done by the engineer tool \u2014 your role is only to investigate code and answer questions. Do not attempt workarounds using bash commands (echo, cat, tee, sed, etc.) to write files.
|
|
88377
88405
|
|
|
88406
|
+
CRITICAL - ALWAYS search before answering:
|
|
88407
|
+
You must NEVER answer questions about the codebase from memory or general knowledge. ALWAYS use the search and extract tools first to find the actual code, then base your answer ONLY on what you found. Even if you think you know the answer, you MUST verify it against the actual code. Your answers must be grounded in code evidence, not assumptions.
|
|
88408
|
+
|
|
88378
88409
|
When exploring code:
|
|
88379
88410
|
- Provide clear, concise explanations based on user request
|
|
88380
88411
|
- Find and highlight the most relevant code snippets, if required
|
|
88381
|
-
- Trace function calls and data flow through the system
|
|
88412
|
+
- Trace function calls and data flow through the system \u2014 follow the FULL call chain, not just the entry point
|
|
88382
88413
|
- Try to understand the user's intent and provide relevant information
|
|
88383
88414
|
- Understand high level picture
|
|
88384
88415
|
- Balance detail with clarity in your explanations
|
|
88416
|
+
- Search using SYNONYMS and alternative terms \u2014 code naming often differs from the concept name (e.g., "authentication" might be named verify_credentials, check_token, validate_session)
|
|
88417
|
+
- When you find a key function, look at what it CALLS and what CALLS it to discover the complete picture
|
|
88418
|
+
- Before answering, ask yourself: "Did I cover all the major components? Are there related subsystems I missed?" If yes, do one more search round.
|
|
88385
88419
|
|
|
88386
88420
|
When providing answers:
|
|
88421
|
+
- Be EXHAUSTIVE: cover ALL components you discovered, not just the main ones. If you found 10 related files, discuss all 10, not just the top 3. Users want the complete picture.
|
|
88422
|
+
- After drafting your answer, do a self-check: "What did I find in my searches that I haven't mentioned yet?" Add any missing components.
|
|
88423
|
+
- Include data structures, configuration options, and error handling \u2014 not just the happy path.
|
|
88387
88424
|
- Always include a "References" section at the end of your response
|
|
88388
88425
|
- List all relevant source code locations you found during exploration
|
|
88389
88426
|
- Use the format: file_path:line_number or file_path#symbol_name
|
|
88390
88427
|
- Group references by file when multiple locations are from the same file
|
|
88391
88428
|
- Include brief descriptions of what each reference contains`,
|
|
88392
|
-
"code-searcher": `You are ProbeChat Code
|
|
88429
|
+
"code-searcher": `You are ProbeChat Code Explorer & Searcher. Your job is to EXPLORE the codebase to find ALL relevant code locations for the query, then return them as JSON targets.
|
|
88430
|
+
|
|
88431
|
+
You think like a code explorer \u2014 you understand that codebases have layers:
|
|
88432
|
+
- Core implementations (algorithms, data structures)
|
|
88433
|
+
- Middleware/integration layers (request handlers, interceptors)
|
|
88434
|
+
- Configuration and storage backends
|
|
88435
|
+
- Scoping mechanisms (per-user, per-org, per-tenant, global)
|
|
88436
|
+
- Supporting utilities and helpers
|
|
88393
88437
|
|
|
88394
88438
|
When searching:
|
|
88395
|
-
-
|
|
88396
|
-
-
|
|
88397
|
-
-
|
|
88439
|
+
- Search for the MAIN concept first, then think: "what RELATED subsystems would a real codebase have?"
|
|
88440
|
+
- Use extract to READ the code you find \u2014 look for function calls, type references, and imports that point to OTHER relevant code
|
|
88441
|
+
- If you find middleware, check: are there org-level or tenant-level variants?
|
|
88442
|
+
- If you find algorithms, check: are there different storage backends?
|
|
88443
|
+
- Search results are paginated \u2014 if results look relevant, call nextPage=true to check for more files
|
|
88444
|
+
- Stop paginating when results become irrelevant or you see "All results retrieved"
|
|
88445
|
+
- Search using SYNONYMS \u2014 code naming differs from concepts (e.g., "rate limiting" \u2192 throttle, quota, limiter, bucket)
|
|
88398
88446
|
|
|
88399
88447
|
Output format (MANDATORY):
|
|
88400
88448
|
- Return ONLY valid JSON with a single top-level key: "targets"
|
|
@@ -88404,7 +88452,8 @@ Output format (MANDATORY):
|
|
|
88404
88452
|
- "path/to/file.ext:line"
|
|
88405
88453
|
- "path/to/file.ext:start-end"
|
|
88406
88454
|
- Prefer #SymbolName when a function/class name is clear; otherwise use line numbers
|
|
88407
|
-
- Deduplicate targets and keep them concise
|
|
88455
|
+
- Deduplicate targets and keep them concise
|
|
88456
|
+
- Aim for 5-15 targets covering ALL aspects of the query`,
|
|
88408
88457
|
"architect": `You are ProbeChat Architect, a specialized AI assistant focused on software architecture and design. Your primary function is to help users understand, analyze, and design software systems using the provided code analysis tools.
|
|
88409
88458
|
|
|
88410
88459
|
When analyzing code:
|
|
@@ -101777,9 +101826,9 @@ Workspace: ${this.allowedFolders.join(", ")}`;
|
|
|
101777
101826
|
Follow these instructions carefully:
|
|
101778
101827
|
1. Analyze the user's request.
|
|
101779
101828
|
2. Use the available tools step-by-step to fulfill the request.
|
|
101780
|
-
3. You
|
|
101781
|
-
4. Ensure to get really deep and understand the full picture before answering.
|
|
101782
|
-
5. Once the task is fully completed, provide your final answer directly as text.
|
|
101829
|
+
3. You MUST use the search tool before answering ANY code-related question. NEVER answer from memory or general knowledge \u2014 your answers must be grounded in actual code found via search/extract.${this.searchDelegate ? " Ask natural language questions \u2014 the search subagent handles keyword formulation and returns extracted code blocks. Use extract only to expand context or read full files." : " Search handles stemming and case variations automatically \u2014 do NOT try keyword variations manually. Read full files only if really necessary."}
|
|
101830
|
+
4. Ensure to get really deep and understand the full picture before answering. Follow call chains \u2014 if function A calls B, search for B too. Look for related subsystems (e.g., if asked about rate limiting, also check for quota, throttling, smoothing).
|
|
101831
|
+
5. Once the task is fully completed, provide your final answer directly as text. Always cite specific files and line numbers as evidence. Do NOT output planning or thinking text \u2014 go straight to the answer.
|
|
101783
101832
|
6. ${this.searchDelegate ? "Ask clear, specific questions when searching. Each search should target a distinct concept or question." : "Prefer concise and focused search queries. Use specific keywords and phrases to narrow down results."}
|
|
101784
101833
|
7. NEVER use bash for code exploration (no grep, cat, find, head, tail, awk, sed) \u2014 always use search and extract tools instead. Bash is only for system operations like building, running tests, or git commands.${this.allowEdit ? `
|
|
101785
101834
|
7. When modifying files, choose the appropriate tool:
|
|
@@ -102165,6 +102214,22 @@ You are working with a workspace. Available paths: ${workspaceDesc}
|
|
|
102165
102214
|
if (recentTexts.every((t) => t && t === recentTexts[0])) return true;
|
|
102166
102215
|
if (recentTexts.every((t) => detectStuckResponse(t))) return true;
|
|
102167
102216
|
}
|
|
102217
|
+
if (steps.length >= 3) {
|
|
102218
|
+
const last3 = steps.slice(-3);
|
|
102219
|
+
const allHaveTools = last3.every((s) => s.toolCalls?.length === 1);
|
|
102220
|
+
if (allHaveTools) {
|
|
102221
|
+
const signatures = last3.map((s) => {
|
|
102222
|
+
const tc = s.toolCalls[0];
|
|
102223
|
+
return `${tc.toolName}::${JSON.stringify(tc.args ?? tc.input)}`;
|
|
102224
|
+
});
|
|
102225
|
+
if (signatures[0] === signatures[1] && signatures[1] === signatures[2]) {
|
|
102226
|
+
if (this.debug) {
|
|
102227
|
+
console.log(`[DEBUG] Circuit breaker: 3 consecutive identical tool calls detected (${last3[0].toolCalls[0].toolName}), forcing stop`);
|
|
102228
|
+
}
|
|
102229
|
+
return true;
|
|
102230
|
+
}
|
|
102231
|
+
}
|
|
102232
|
+
}
|
|
102168
102233
|
return false;
|
|
102169
102234
|
},
|
|
102170
102235
|
prepareStep: ({ steps, stepNumber }) => {
|
|
@@ -102173,6 +102238,22 @@ You are working with a workspace. Available paths: ${workspaceDesc}
|
|
|
102173
102238
|
toolChoice: "none"
|
|
102174
102239
|
};
|
|
102175
102240
|
}
|
|
102241
|
+
if (steps.length >= 2) {
|
|
102242
|
+
const last2 = steps.slice(-2);
|
|
102243
|
+
if (last2.every((s) => s.toolCalls?.length === 1)) {
|
|
102244
|
+
const tc1 = last2[0].toolCalls[0];
|
|
102245
|
+
const tc2 = last2[1].toolCalls[0];
|
|
102246
|
+
const sig1 = `${tc1.toolName}::${JSON.stringify(tc1.args ?? tc1.input)}`;
|
|
102247
|
+
const sig2 = `${tc2.toolName}::${JSON.stringify(tc2.args ?? tc2.input)}`;
|
|
102248
|
+
if (sig1 === sig2) {
|
|
102249
|
+
if (this.debug) {
|
|
102250
|
+
console.log(`[DEBUG] prepareStep: 2 consecutive identical tool calls (${tc1.toolName}), forcing toolChoice=none`);
|
|
102251
|
+
console.log(`[DEBUG] sig: ${sig1.substring(0, 200)}`);
|
|
102252
|
+
}
|
|
102253
|
+
return { toolChoice: "none" };
|
|
102254
|
+
}
|
|
102255
|
+
}
|
|
102256
|
+
}
|
|
102176
102257
|
const lastStep = steps[steps.length - 1];
|
|
102177
102258
|
const modelJustStopped = lastStep?.finishReason === "stop" && (!lastStep?.toolCalls || lastStep.toolCalls.length === 0);
|
|
102178
102259
|
if (modelJustStopped) {
|
|
@@ -102203,7 +102284,9 @@ ${resultToReview}
|
|
|
102203
102284
|
|
|
102204
102285
|
Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If something needs to be fixed or is missing, do it now, then respond with the COMPLETE updated answer (everything you did in total, not just the fix).`;
|
|
102205
102286
|
return {
|
|
102206
|
-
userMessage: completionPromptMessage
|
|
102287
|
+
userMessage: completionPromptMessage,
|
|
102288
|
+
toolChoice: "none"
|
|
102289
|
+
// Force text-only review — no tool calls
|
|
102207
102290
|
};
|
|
102208
102291
|
}
|
|
102209
102292
|
}
|
|
@@ -102245,7 +102328,11 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
102245
102328
|
options.onStream(text);
|
|
102246
102329
|
}
|
|
102247
102330
|
if (this.debug) {
|
|
102248
|
-
|
|
102331
|
+
const toolSummary = toolCalls?.length ? toolCalls.map((tc) => {
|
|
102332
|
+
const args = tc.args ? JSON.stringify(tc.args) : "";
|
|
102333
|
+
return args ? `${tc.toolName}(${debugTruncate(args, 120)})` : tc.toolName;
|
|
102334
|
+
}).join(", ") : "none";
|
|
102335
|
+
console.log(`[DEBUG] Step ${currentIteration}/${maxIterations} finished (reason: ${finishReason}, tools: [${toolSummary}])`);
|
|
102249
102336
|
if (text) {
|
|
102250
102337
|
console.log(`[DEBUG] model text: ${debugTruncate(text)}`);
|
|
102251
102338
|
}
|
|
@@ -102278,9 +102365,15 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
102278
102365
|
}
|
|
102279
102366
|
const executeAIRequest = async () => {
|
|
102280
102367
|
const result = await this.streamTextWithRetryAndFallback(streamOptions);
|
|
102281
|
-
const
|
|
102368
|
+
const steps = await result.steps;
|
|
102369
|
+
let finalText;
|
|
102370
|
+
if (steps && steps.length > 1) {
|
|
102371
|
+
const lastStepText = steps[steps.length - 1].text;
|
|
102372
|
+
finalText = lastStepText || await result.text;
|
|
102373
|
+
} else {
|
|
102374
|
+
finalText = await result.text;
|
|
102375
|
+
}
|
|
102282
102376
|
if (this.debug) {
|
|
102283
|
-
const steps = await result.steps;
|
|
102284
102377
|
console.log(`[DEBUG] streamText completed: ${steps?.length || 0} steps, finalText=${finalText?.length || 0} chars`);
|
|
102285
102378
|
}
|
|
102286
102379
|
const usage = await result.usage;
|
|
@@ -102350,12 +102443,12 @@ ${finalResult}
|
|
|
102350
102443
|
|
|
102351
102444
|
Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If something needs to be fixed or is missing, do it now, then respond with the COMPLETE updated answer (everything you did in total, not just the fix).`;
|
|
102352
102445
|
currentMessages.push({ role: "user", content: completionPromptMessage });
|
|
102353
|
-
const completionMaxIterations = 5;
|
|
102354
102446
|
const completionStreamOptions = {
|
|
102355
102447
|
model: this.provider ? this.provider(this.model) : this.model,
|
|
102356
102448
|
messages: this.prepareMessagesWithImages(currentMessages),
|
|
102357
102449
|
tools: tools2,
|
|
102358
|
-
|
|
102450
|
+
toolChoice: "none",
|
|
102451
|
+
// Force text-only response — no tool calls during review
|
|
102359
102452
|
maxTokens: maxResponseTokens,
|
|
102360
102453
|
temperature: 0.3,
|
|
102361
102454
|
onStepFinish: ({ toolResults, text, finishReason, usage }) => {
|