@probelabs/probe 0.6.0-rc295 → 0.6.0-rc296

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 (31) hide show
  1. package/README.md +7 -0
  2. package/bin/binaries/{probe-v0.6.0-rc295-aarch64-apple-darwin.tar.gz → probe-v0.6.0-rc296-aarch64-apple-darwin.tar.gz} +0 -0
  3. package/bin/binaries/{probe-v0.6.0-rc295-aarch64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc296-aarch64-unknown-linux-musl.tar.gz} +0 -0
  4. package/bin/binaries/{probe-v0.6.0-rc295-x86_64-apple-darwin.tar.gz → probe-v0.6.0-rc296-x86_64-apple-darwin.tar.gz} +0 -0
  5. package/bin/binaries/{probe-v0.6.0-rc295-x86_64-pc-windows-msvc.zip → probe-v0.6.0-rc296-x86_64-pc-windows-msvc.zip} +0 -0
  6. package/bin/binaries/{probe-v0.6.0-rc295-x86_64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc296-x86_64-unknown-linux-musl.tar.gz} +0 -0
  7. package/build/agent/ProbeAgent.d.ts +8 -2
  8. package/build/agent/ProbeAgent.js +683 -10
  9. package/build/agent/mcp/client.js +81 -4
  10. package/build/agent/mcp/xmlBridge.js +11 -0
  11. package/build/agent/otelLogBridge.js +184 -0
  12. package/build/agent/simpleTelemetry.js +8 -0
  13. package/build/delegate.js +75 -6
  14. package/build/index.js +6 -2
  15. package/build/tools/common.js +84 -11
  16. package/build/tools/vercel.js +78 -18
  17. package/cjs/agent/ProbeAgent.cjs +858 -32
  18. package/cjs/agent/simpleTelemetry.cjs +112 -0
  19. package/cjs/index.cjs +970 -32
  20. package/index.d.ts +26 -0
  21. package/package.json +1 -1
  22. package/src/agent/ProbeAgent.d.ts +8 -2
  23. package/src/agent/ProbeAgent.js +683 -10
  24. package/src/agent/mcp/client.js +81 -4
  25. package/src/agent/mcp/xmlBridge.js +11 -0
  26. package/src/agent/otelLogBridge.js +184 -0
  27. package/src/agent/simpleTelemetry.js +8 -0
  28. package/src/delegate.js +75 -6
  29. package/src/index.js +6 -2
  30. package/src/tools/common.js +84 -11
  31. package/src/tools/vercel.js +78 -18
@@ -254,6 +254,10 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
254
254
  '- Use exact=true when searching for a KNOWN symbol name (function, type, variable, struct).',
255
255
  '- exact=true matches the literal string only — no stemming, no splitting.',
256
256
  '- This is ideal for precise lookups: exact=true "ForwardMessage", exact=true "SessionLimiter", exact=true "ThrottleRetryLimit".',
257
+ '- IMPORTANT: Use exact=true when searching for strings containing punctuation, quotes, or empty values.',
258
+ ' Default BM25 search strips punctuation and treats quoted empty strings as noise.',
259
+ ' Example: searching for \'description: ""\' with exact=false will NOT find empty description fields — it just matches "description".',
260
+ ' Use exact=true for literal patterns like \'description: ""\', \'value: \\\'\\\'\', or any YAML/config field with specific punctuation.',
257
261
  '- Do NOT use exact=true for exploratory/conceptual queries — use the default for those.',
258
262
  '',
259
263
  'Combining searches with OR:',
@@ -313,7 +317,13 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
313
317
  'WHEN TO STOP:',
314
318
  '- After you have explored the main concept AND related subsystems.',
315
319
  '- Once you have 5-15 targets covering different aspects of the query.',
316
- '- If you get a "DUPLICATE SEARCH BLOCKED" message, move on.',
320
+ '- If you get a "DUPLICATE SEARCH BLOCKED" message, do NOT rephrase the same query — try a FUNDAMENTALLY different approach:',
321
+ ' * Switch between exact=true and exact=false',
322
+ ' * Search for a broader term and filter results manually',
323
+ ' * Use listFiles to browse the directory structure directly',
324
+ ' * Look for related/surrounding patterns instead of the exact string',
325
+ '- If 2-3 genuinely different search approaches fail, STOP and report what you tried and why it failed.',
326
+ ' Do NOT keep trying variations of the same failing concept.',
317
327
  '',
318
328
  'Strategy:',
319
329
  '1. Analyze the query — identify key concepts, then brainstorm SYNONYMS and alternative terms for each.',
@@ -371,10 +381,10 @@ export const searchTool = (options = {}) => {
371
381
  return result;
372
382
  };
373
383
 
374
- // Track previous non-paginated searches to detect and block duplicates
375
- const previousSearches = new Set();
376
- // Track how many times a duplicate search has been blocked (for escalating messages)
377
- let consecutiveDupBlocks = 0;
384
+ // Track previous non-paginated searches: key { hadResults: boolean }
385
+ const previousSearches = new Map();
386
+ // Track per-key consecutive block counts (not global, to avoid cross-query pollution)
387
+ const dupBlockCounts = new Map();
378
388
  // Track pagination counts per query to cap runaway pagination
379
389
  const paginationCounts = new Map();
380
390
  const MAX_PAGES_PER_QUERY = 3;
@@ -444,22 +454,28 @@ export const searchTool = (options = {}) => {
444
454
  if (!searchDelegate) {
445
455
  // Block duplicate non-paginated searches (models sometimes repeat the exact same call)
446
456
  // Allow pagination: only nextPage=true is a legitimate repeat of the same query
447
- // Use query+exact as the key (ignore path) to prevent path-hopping evasion
448
- // where model searches same term on different subpaths hoping for different results
449
- const searchKey = `${searchQuery}::${exact || false}`;
457
+ // Include path in dedup key so same query across different repos is allowed (#520)
458
+ const searchKey = `${searchPath}::${searchQuery}::${exact || false}`;
450
459
  if (!nextPage) {
451
460
  if (previousSearches.has(searchKey)) {
452
- consecutiveDupBlocks++;
461
+ const blockCount = (dupBlockCounts.get(searchKey) || 0) + 1;
462
+ dupBlockCounts.set(searchKey, blockCount);
453
463
  if (debug) {
454
- console.error(`[DEDUP] Blocked duplicate search (${consecutiveDupBlocks}x): "${searchQuery}" (path: "${searchPath}")`);
464
+ console.error(`[DEDUP] Blocked duplicate search (${blockCount}x): "${searchQuery}" (path: "${searchPath}")`);
455
465
  }
456
- if (consecutiveDupBlocks >= 3) {
457
- 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.';
466
+ if (blockCount >= 3) {
467
+ return 'STOP. You have been blocked ' + blockCount + ' times for repeating the same search. You MUST provide your final answer NOW with whatever information you have. Do NOT call any more tools.';
458
468
  }
459
- 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.';
469
+ const prev = previousSearches.get(searchKey);
470
+ if (prev.hadResults) {
471
+ return `DUPLICATE SEARCH BLOCKED (${blockCount}x). You already searched for "${searchQuery}" in this path and found results. Do NOT repeat. Use extract to examine the files you already found, try a COMPLETELY different keyword, or provide your final answer.`;
472
+ }
473
+ const exactHint = exact
474
+ ? 'You used exact=true. Try a broader search with exact=false, or use listFiles to browse the directory structure.'
475
+ : 'Try exact=true if you need literal/punctuation matching (e.g. \'description: ""\'), or use listFiles to explore directories, or search for a broader/related term and filter manually.';
476
+ return `DUPLICATE SEARCH BLOCKED (${blockCount}x). You already searched for "${searchQuery}" in this path and got NO results. This term does not appear in the codebase. Do NOT repeat or rephrase — try a FUNDAMENTALLY different approach: ${exactHint} If multiple approaches have failed, provide your final answer with what you know.`;
460
477
  }
461
- previousSearches.add(searchKey);
462
- consecutiveDupBlocks = 0; // Reset on successful new search
478
+ previousSearches.set(searchKey, { hadResults: false });
463
479
  paginationCounts.set(searchKey, 0);
464
480
  } else {
465
481
  // Cap pagination to prevent runaway page-through of broad queries
@@ -474,6 +490,16 @@ export const searchTool = (options = {}) => {
474
490
  }
475
491
  try {
476
492
  const result = maybeAnnotate(await runRawSearch());
493
+ // Track whether this search had results for better dedup messages
494
+ if (typeof result === 'string' && result.includes('No results found')) {
495
+ // Append contextual hint for ticket/issue ID queries
496
+ if (/^[A-Z]+-\d+$/.test(searchQuery.trim()) || /^[A-Z]+-\d+$/.test(searchQuery.replace(/"/g, '').trim())) {
497
+ return result + '\n\n⚠️ Your query looks like a ticket/issue ID (e.g., JIRA-1234). Ticket IDs are rarely present in source code. Search for the technical concepts described in the ticket instead (e.g., function names, error messages, variable names).';
498
+ }
499
+ } else if (typeof result === 'string') {
500
+ const entry = previousSearches.get(searchKey);
501
+ if (entry) entry.hadResults = true;
502
+ }
477
503
  // Track files found in search results for staleness detection
478
504
  if (options.fileTracker && typeof result === 'string') {
479
505
  options.fileTracker.trackFilesFromOutput(result, effectiveSearchCwd).catch(() => {});
@@ -862,7 +888,11 @@ export const extractTool = (options = {}) => {
862
888
  * @returns {Object} Configured delegate tool
863
889
  */
864
890
  export const delegateTool = (options = {}) => {
865
- const { debug = false, timeout = 300, cwd, allowedFolders, workspaceRoot, enableBash = false, bashConfig, architectureFileName, enableMcp = false, mcpConfig = null, mcpConfigPath = null, delegationManager = null } = options;
891
+ const { debug = false, timeout = 300, cwd, allowedFolders, workspaceRoot, enableBash = false, bashConfig, architectureFileName, enableMcp = false, mcpConfig = null, mcpConfigPath = null, delegationManager = null,
892
+ // Timeout settings inherited from parent agent
893
+ timeoutBehavior, maxOperationTimeout, requestTimeout, gracefulTimeoutBonusSteps,
894
+ negotiatedTimeoutBudget, negotiatedTimeoutMaxRequests, negotiatedTimeoutMaxPerRequest,
895
+ parentOperationStartTime, onSubagentCreated, onSubagentCompleted } = options;
866
896
 
867
897
  return tool({
868
898
  name: 'delegate',
@@ -941,9 +971,32 @@ export const delegateTool = (options = {}) => {
941
971
  }
942
972
 
943
973
  // Execute delegation - let errors propagate naturally
974
+ // Cap delegate timeout to remaining parent budget (with 10% headroom)
975
+ let effectiveTimeout = timeout;
976
+ if (parentOperationStartTime && maxOperationTimeout) {
977
+ const elapsed = Date.now() - parentOperationStartTime;
978
+ const remaining = maxOperationTimeout - elapsed;
979
+ const budgetCap = Math.max(30, Math.floor(remaining * 0.9 / 1000)); // seconds, min 30s
980
+ if (budgetCap < effectiveTimeout) {
981
+ effectiveTimeout = budgetCap;
982
+ if (debug) {
983
+ console.error(`[DELEGATE] Capping timeout from ${timeout}s to ${effectiveTimeout}s (remaining parent budget: ${Math.floor(remaining/1000)}s)`);
984
+ }
985
+ if (tracer) {
986
+ tracer.addEvent('delegation.budget_capped', {
987
+ 'delegation.original_timeout_s': timeout,
988
+ 'delegation.effective_timeout_s': effectiveTimeout,
989
+ 'delegation.parent_elapsed_ms': elapsed,
990
+ 'delegation.parent_remaining_ms': remaining,
991
+ 'delegation.parent_session_id': parentSessionId,
992
+ });
993
+ }
994
+ }
995
+ }
996
+
944
997
  const result = await delegate({
945
998
  task,
946
- timeout,
999
+ timeout: effectiveTimeout,
947
1000
  debug,
948
1001
  currentIteration: currentIteration || 0,
949
1002
  maxIterations: maxIterations || 30,
@@ -961,7 +1014,14 @@ export const delegateTool = (options = {}) => {
961
1014
  mcpConfig,
962
1015
  mcpConfigPath,
963
1016
  delegationManager, // Per-instance delegation limits
964
- parentAbortSignal
1017
+ parentAbortSignal,
1018
+ // Inherit timeout settings for subagent
1019
+ timeoutBehavior,
1020
+ requestTimeout,
1021
+ gracefulTimeoutBonusSteps,
1022
+ // Subagent lifecycle callbacks for graceful stop coordination
1023
+ onSubagentCreated,
1024
+ onSubagentCompleted,
965
1025
  });
966
1026
 
967
1027
  return result;