@probelabs/probe 0.6.0-rc279 → 0.6.0-rc280

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.
@@ -29663,9 +29663,7 @@ async function acquireFileLock(lockPath, version2) {
29663
29663
  };
29664
29664
  try {
29665
29665
  await import_fs_extra2.default.writeFile(lockPath, JSON.stringify(lockData), { flag: "wx" });
29666
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
29667
- console.log(`Acquired file lock: ${lockPath}`);
29668
- }
29666
+ console.log(`Acquired file lock: ${lockPath}`);
29669
29667
  return true;
29670
29668
  } catch (error2) {
29671
29669
  if (error2.code === "EEXIST") {
@@ -29673,15 +29671,11 @@ async function acquireFileLock(lockPath, version2) {
29673
29671
  const existingLock = JSON.parse(await import_fs_extra2.default.readFile(lockPath, "utf-8"));
29674
29672
  const lockAge = Date.now() - existingLock.timestamp;
29675
29673
  if (lockAge > LOCK_TIMEOUT_MS) {
29676
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
29677
- console.log(`Removing stale lock file (age: ${Math.round(lockAge / 1e3)}s, pid: ${existingLock.pid})`);
29678
- }
29674
+ console.log(`Removing stale lock file (age: ${Math.round(lockAge / 1e3)}s, pid: ${existingLock.pid})`);
29679
29675
  await import_fs_extra2.default.remove(lockPath);
29680
29676
  return false;
29681
29677
  }
29682
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
29683
- console.log(`Download in progress by process ${existingLock.pid}, waiting...`);
29684
- }
29678
+ console.log(`Download in progress by process ${existingLock.pid}, waiting...`);
29685
29679
  return false;
29686
29680
  } catch (readError) {
29687
29681
  if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
@@ -29722,36 +29716,36 @@ async function releaseFileLock(lockPath) {
29722
29716
  }
29723
29717
  async function waitForFileLock(lockPath, binaryPath) {
29724
29718
  const startTime = Date.now();
29719
+ let lastStatusTime = startTime;
29720
+ console.log(`Waiting for file lock to clear: ${lockPath}`);
29725
29721
  while (Date.now() - startTime < MAX_LOCK_WAIT_MS) {
29726
29722
  if (await import_fs_extra2.default.pathExists(binaryPath)) {
29727
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
29728
- console.log(`Binary now available at ${binaryPath}, download completed by another process`);
29729
- }
29723
+ const waitedSeconds = Math.round((Date.now() - startTime) / 1e3);
29724
+ console.log(`Binary now available at ${binaryPath}, download completed by another process (waited ${waitedSeconds}s)`);
29730
29725
  return true;
29731
29726
  }
29732
29727
  const lockExists = await import_fs_extra2.default.pathExists(lockPath);
29733
29728
  if (!lockExists) {
29734
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
29735
- console.log(`Lock file removed but binary not found - download may have failed`);
29736
- }
29729
+ console.log(`Lock file removed but binary not found - download may have failed`);
29737
29730
  return false;
29738
29731
  }
29739
29732
  try {
29740
29733
  const lockData = JSON.parse(await import_fs_extra2.default.readFile(lockPath, "utf-8"));
29741
29734
  const lockAge = Date.now() - lockData.timestamp;
29742
29735
  if (lockAge > LOCK_TIMEOUT_MS) {
29743
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
29744
- console.log(`Lock expired (age: ${Math.round(lockAge / 1e3)}s), will retry download`);
29745
- }
29736
+ console.log(`Lock expired (age: ${Math.round(lockAge / 1e3)}s), will retry download`);
29746
29737
  return false;
29747
29738
  }
29748
29739
  } catch {
29749
29740
  }
29741
+ if (Date.now() - lastStatusTime >= 15e3) {
29742
+ const elapsedSeconds = Math.round((Date.now() - startTime) / 1e3);
29743
+ console.log(`Still waiting for file lock (${elapsedSeconds}s/${MAX_LOCK_WAIT_MS / 1e3}s max)`);
29744
+ lastStatusTime = Date.now();
29745
+ }
29750
29746
  await new Promise((resolve8) => setTimeout(resolve8, LOCK_POLL_INTERVAL_MS));
29751
29747
  }
29752
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
29753
- console.log(`Timeout waiting for file lock`);
29754
- }
29748
+ console.log(`Timeout waiting for file lock after ${MAX_LOCK_WAIT_MS / 1e3}s`);
29755
29749
  return false;
29756
29750
  }
29757
29751
  async function withDownloadLock(version2, downloadFn) {
@@ -29765,9 +29759,7 @@ async function withDownloadLock(version2, downloadFn) {
29765
29759
  }
29766
29760
  downloadLocks.delete(lockKey);
29767
29761
  } else {
29768
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
29769
- console.log(`Download already in progress in this process for version ${lockKey}, waiting...`);
29770
- }
29762
+ console.log(`Download already in progress in this process for version ${lockKey}, waiting...`);
29771
29763
  try {
29772
29764
  return await lock.promise;
29773
29765
  } catch (error2) {
@@ -29777,10 +29769,16 @@ async function withDownloadLock(version2, downloadFn) {
29777
29769
  }
29778
29770
  }
29779
29771
  }
29772
+ let timeoutId = null;
29780
29773
  const downloadPromise = Promise.race([
29781
29774
  downloadFn(),
29782
29775
  new Promise(
29783
- (_, reject2) => setTimeout(() => reject2(new Error(`Download timeout after ${LOCK_TIMEOUT_MS / 1e3}s`)), LOCK_TIMEOUT_MS)
29776
+ (_, reject2) => {
29777
+ timeoutId = setTimeout(() => reject2(new Error(`Download timeout after ${LOCK_TIMEOUT_MS / 1e3}s`)), LOCK_TIMEOUT_MS);
29778
+ if (timeoutId.unref) {
29779
+ timeoutId.unref();
29780
+ }
29781
+ }
29784
29782
  )
29785
29783
  ]);
29786
29784
  downloadLocks.set(lockKey, {
@@ -29791,6 +29789,9 @@ async function withDownloadLock(version2, downloadFn) {
29791
29789
  const result = await downloadPromise;
29792
29790
  return result;
29793
29791
  } finally {
29792
+ if (timeoutId) {
29793
+ clearTimeout(timeoutId);
29794
+ }
29794
29795
  downloadLocks.delete(lockKey);
29795
29796
  }
29796
29797
  }
@@ -31522,10 +31523,9 @@ var init_delegate = __esm({
31522
31523
  if (this.tryAcquire(parentSessionId)) {
31523
31524
  return true;
31524
31525
  }
31525
- if (debug) {
31526
- console.error(`[DelegationManager] Slot unavailable (${this.globalActive}/${this.maxConcurrent}), queuing... (queue size: ${this.waitQueue.length}, timeout: ${effectiveTimeout}ms)`);
31527
- }
31526
+ console.error(`[DelegationManager] Slot unavailable (${this.globalActive}/${this.maxConcurrent}), queuing... (queue size: ${this.waitQueue.length + 1}, timeout: ${effectiveTimeout}ms)`);
31528
31527
  return new Promise((resolve8, reject2) => {
31528
+ const queuedAt = Date.now();
31529
31529
  const entry = {
31530
31530
  resolve: null,
31531
31531
  // Will be wrapped below
@@ -31533,20 +31533,23 @@ var init_delegate = __esm({
31533
31533
  // Will be wrapped below
31534
31534
  parentSessionId,
31535
31535
  debug,
31536
- queuedAt: Date.now(),
31537
- timeoutId: null
31536
+ queuedAt,
31537
+ timeoutId: null,
31538
+ reminderId: null
31538
31539
  };
31539
31540
  let settled = false;
31540
31541
  entry.resolve = (value) => {
31541
31542
  if (settled) return;
31542
31543
  settled = true;
31543
31544
  if (entry.timeoutId) clearTimeout(entry.timeoutId);
31545
+ if (entry.reminderId) clearInterval(entry.reminderId);
31544
31546
  resolve8(value);
31545
31547
  };
31546
31548
  entry.reject = (error2) => {
31547
31549
  if (settled) return;
31548
31550
  settled = true;
31549
31551
  if (entry.timeoutId) clearTimeout(entry.timeoutId);
31552
+ if (entry.reminderId) clearInterval(entry.reminderId);
31550
31553
  reject2(error2);
31551
31554
  };
31552
31555
  if (effectiveTimeout > 0) {
@@ -31558,6 +31561,13 @@ var init_delegate = __esm({
31558
31561
  entry.reject(new Error(`Delegation queue timeout: waited ${effectiveTimeout}ms for an available slot`));
31559
31562
  }, effectiveTimeout);
31560
31563
  }
31564
+ entry.reminderId = setInterval(() => {
31565
+ const waitedSeconds = Math.round((Date.now() - queuedAt) / 1e3);
31566
+ console.error(`[DelegationManager] Still waiting for slot (${waitedSeconds}s). ${this.globalActive}/${this.maxConcurrent} active, ${this.waitQueue.length} queued.`);
31567
+ }, 15e3);
31568
+ if (entry.reminderId.unref) {
31569
+ entry.reminderId.unref();
31570
+ }
31561
31571
  this.waitQueue.push(entry);
31562
31572
  });
31563
31573
  }
@@ -31596,18 +31606,14 @@ var init_delegate = __esm({
31596
31606
  const sessionData = this.sessionDelegations.get(parentSessionId);
31597
31607
  const sessionCount = sessionData?.count || 0;
31598
31608
  if (sessionCount >= this.maxPerSession) {
31599
- if (debug) {
31600
- console.error(`[DelegationManager] Session limit (${this.maxPerSession}) reached for queued item, rejecting`);
31601
- }
31609
+ console.error(`[DelegationManager] Session limit (${this.maxPerSession}) reached for queued item, rejecting`);
31602
31610
  toReject.push({ reject: reject2, error: new Error(`Maximum delegations per session (${this.maxPerSession}) reached for session ${parentSessionId}`) });
31603
31611
  continue;
31604
31612
  }
31605
31613
  }
31606
31614
  this._incrementCounters(parentSessionId);
31607
- if (debug) {
31608
- const waitTime = Date.now() - queuedAt;
31609
- console.error(`[DelegationManager] Granted slot from queue (waited ${waitTime}ms). Active: ${this.globalActive}/${this.maxConcurrent}`);
31610
- }
31615
+ const waitTime = Date.now() - queuedAt;
31616
+ console.error(`[DelegationManager] Granted slot from queue (waited ${waitTime}ms). Active: ${this.globalActive}/${this.maxConcurrent}`);
31611
31617
  toResolve.push(resolve8);
31612
31618
  }
31613
31619
  if (toResolve.length > 0 || toReject.length > 0) {
@@ -31657,6 +31663,9 @@ var init_delegate = __esm({
31657
31663
  if (entry.timeoutId) {
31658
31664
  clearTimeout(entry.timeoutId);
31659
31665
  }
31666
+ if (entry.reminderId) {
31667
+ clearInterval(entry.reminderId);
31668
+ }
31660
31669
  if (entry.reject) {
31661
31670
  entry.reject(new Error("DelegationManager was cleaned up"));
31662
31671
  }
@@ -31801,16 +31810,12 @@ async function processChunksParallel(chunks, extractionPrompt, maxWorkers, optio
31801
31810
  return result;
31802
31811
  });
31803
31812
  active.add(promise);
31804
- if (options.debug) {
31805
- console.error(`[analyze_all] Started processing chunk ${chunk.id}/${chunk.total}`);
31806
- }
31813
+ console.error(`[analyze_all] Started processing chunk ${chunk.id}/${chunk.total}`);
31807
31814
  }
31808
31815
  if (active.size > 0) {
31809
31816
  const result = await Promise.race(active);
31810
31817
  results.push(result);
31811
- if (options.debug) {
31812
- console.error(`[analyze_all] Completed chunk ${result.chunk.id}/${result.chunk.total}`);
31813
- }
31818
+ console.error(`[analyze_all] Completed chunk ${result.chunk.id}/${result.chunk.total}`);
31814
31819
  }
31815
31820
  }
31816
31821
  results.sort((a5, b5) => a5.chunk.id - b5.chunk.id);
@@ -36303,14 +36308,14 @@ function resolveTargetPath(target, cwd) {
36303
36308
  }
36304
36309
  return filePart + suffix;
36305
36310
  }
36306
- var import_path5, searchSchema, searchAllSchema, querySchema, extractSchema, delegateSchema, listSkillsSchema, useSkillSchema, listFilesSchema, searchFilesSchema, readImageSchema, bashSchema, analyzeAllSchema, executePlanSchema, cleanupExecutePlanSchema, searchDescription, queryDescription, extractDescription, delegateDescription, analyzeAllDescription;
36311
+ var import_path5, searchSchema, searchAllSchema, querySchema, extractSchema, delegateSchema, listSkillsSchema, useSkillSchema, listFilesSchema, searchFilesSchema, readImageSchema, bashSchema, analyzeAllSchema, executePlanSchema, cleanupExecutePlanSchema, searchDescription, searchDelegateDescription, queryDescription, extractDescription, delegateDescription, analyzeAllDescription;
36307
36312
  var init_common2 = __esm({
36308
36313
  "src/tools/common.js"() {
36309
36314
  "use strict";
36310
36315
  init_zod();
36311
36316
  import_path5 = require("path");
36312
36317
  searchSchema = external_exports.object({
36313
- query: external_exports.string().describe("Search query with Elasticsearch syntax. Use quotes for exact matches, AND/OR for boolean logic, - for negation."),
36318
+ query: external_exports.string().describe("Search query \u2014 natural language questions or Elasticsearch-style keywords both work. For keywords: use quotes for exact phrases, AND/OR for boolean logic, - for negation. Probe handles stemming and camelCase/snake_case splitting automatically, so do NOT try case or style variations of the same keyword."),
36314
36319
  path: external_exports.string().optional().default(".").describe('Path to search in. For dependencies use "go:github.com/owner/repo", "js:package_name", or "rust:cargo_name" etc.'),
36315
36320
  exact: external_exports.boolean().optional().default(false).describe('Default (false) enables stemming and keyword splitting for exploratory search - "getUserData" matches "get", "user", "data", etc. Set true for precise symbol lookup where "getUserData" matches only "getUserData". Use true when you know the exact symbol name.'),
36316
36321
  maxTokens: external_exports.number().nullable().optional().describe("Maximum tokens to return. Default is 20000. Set to null for unlimited results."),
@@ -36318,7 +36323,7 @@ var init_common2 = __esm({
36318
36323
  nextPage: external_exports.boolean().optional().default(false).describe("Set to true when requesting the next page of results. Requires passing the same session ID from the previous search output.")
36319
36324
  });
36320
36325
  searchAllSchema = external_exports.object({
36321
- query: external_exports.string().describe("Search query with Elasticsearch syntax. Use quotes for exact matches, AND/OR for boolean logic, - for negation."),
36326
+ query: external_exports.string().describe("Search query \u2014 natural language questions or Elasticsearch-style keywords both work. For keywords: use quotes for exact phrases, AND/OR for boolean logic, - for negation. Probe handles stemming and camelCase/snake_case splitting automatically, so do NOT try case or style variations of the same keyword."),
36322
36327
  path: external_exports.string().optional().default(".").describe("Path to search in."),
36323
36328
  exact: external_exports.boolean().optional().default(false).describe("Use exact matching instead of stemming."),
36324
36329
  maxTokensPerPage: external_exports.number().optional().default(2e4).describe("Tokens per page when paginating. Default 20000."),
@@ -36373,7 +36378,8 @@ var init_common2 = __esm({
36373
36378
  clearOutputBuffer: external_exports.boolean().optional().default(true).describe("Clear the output buffer from previous execute_plan calls"),
36374
36379
  clearSessionStore: external_exports.boolean().optional().default(false).describe("Clear the session store (persisted data across execute_plan calls)")
36375
36380
  });
36376
- 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.";
36381
+ 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. NOTE: By default, search handles stemming, case-insensitive matching, and camelCase/snake_case splitting automatically \u2014 do NOT manually try keyword variations like "getAllUsers" then "get_all_users" then "GetAllUsers". One search covers all variations.';
36382
+ searchDelegateDescription = 'Search code in the repository by asking a question. Accepts natural language questions (e.g., "How does authentication work?", "Where is the user validation logic?"). A specialized subagent breaks down your question into targeted keyword searches and returns extracted code blocks. Do NOT formulate keyword queries yourself \u2014 just ask the question naturally.';
36377
36383
  queryDescription = "Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.";
36378
36384
  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.";
36379
36385
  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.";
@@ -36556,11 +36562,41 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
36556
36562
  "- extract: Verify code snippets to ensure targets are actually relevant before including them.",
36557
36563
  "- listFiles: Understand directory structure to find where relevant code might live.",
36558
36564
  "",
36559
- "Strategy for complex queries:",
36565
+ "CRITICAL - How probe search works (do NOT ignore):",
36566
+ "- By default (exact=false), probe ALREADY handles stemming, case-insensitive matching, and camelCase/snake_case splitting.",
36567
+ '- Searching "allowed_ips" ALREADY matches "AllowedIPs", "allowedIps", "allowed_ips", etc. Do NOT manually try case/style variations.',
36568
+ '- Searching "getUserData" ALREADY matches "get", "user", "data" and their variations.',
36569
+ "- NEVER repeat the same search query \u2014 you will get the same results.",
36570
+ "- NEVER search trivial variations of the same keyword (e.g., AllowedIPs then allowedIps then allowed_ips). This is wasteful \u2014 probe handles it.",
36571
+ "- If a search returns no results, the term likely does not exist in that path. Try a genuinely DIFFERENT keyword or concept, not a variation.",
36572
+ "- If 2-3 consecutive searches return no results for a concept, STOP searching for it and move on.",
36573
+ "",
36574
+ "GOOD search strategy (do this):",
36575
+ ' Query: "How does authentication work and how are sessions managed?"',
36576
+ ' \u2192 search "authentication" \u2192 search "session management" (two different concepts)',
36577
+ ' Query: "Find the IP allowlist middleware"',
36578
+ ' \u2192 search "allowlist middleware" (one search, probe handles IP/ip/Ip variations)',
36579
+ ' Query: "How does BM25 scoring work with SIMD optimization?"',
36580
+ ' \u2192 search "BM25 scoring" \u2192 search "SIMD optimization" (two different concepts)',
36581
+ "",
36582
+ "BAD search strategy (never do this):",
36583
+ ' \u2192 search "AllowedIPs" \u2192 search "allowedIps" \u2192 search "allowed_ips" (WRONG: these are trivial case variations, probe handles them)',
36584
+ ' \u2192 search "CIDR" \u2192 search "cidr" \u2192 search "Cidr" \u2192 search "*cidr*" (WRONG: same keyword repeated with variations)',
36585
+ ' \u2192 search "error handling" \u2192 search "error handling" \u2192 search "error handling" (WRONG: repeating exact same query)',
36586
+ "",
36587
+ "Keyword tips:",
36588
+ "- Common programming keywords are filtered as stopwords when unquoted: function, class, return, new, struct, impl, var, let, const, etc.",
36589
+ '- Avoid searching for these alone \u2014 combine with a specific term (e.g., "middleware function" is fine, "function" alone is too generic).',
36590
+ '- To bypass stopword filtering: wrap terms in quotes ("return", "struct") or set exact=true. Both disable stemming and splitting too.',
36591
+ "- Multiple words without operators use OR logic: foo bar = foo OR bar. Use AND explicitly if you need both: foo AND bar.",
36592
+ '- camelCase terms are split: getUserData becomes "get", "user", "data" \u2014 so one search covers all naming styles.',
36593
+ "",
36594
+ "Strategy:",
36560
36595
  "1. Analyze the query - identify key concepts, entities, and relationships",
36561
- '2. Run focused searches for each independent concept (e.g., for "how do payments work and how are emails sent", search "payments" and "emails" separately since they are unrelated)',
36562
- "3. Use extract to verify relevance of promising results",
36563
- "4. Combine all relevant targets in your final response",
36596
+ "2. Run ONE focused search per concept with the most natural keyword. Trust probe to handle variations.",
36597
+ "3. If a search returns results, use extract to verify relevance",
36598
+ "4. Only try a different keyword if the first one returned irrelevant results (not if it returned no results \u2014 that means the concept is absent)",
36599
+ "5. Combine all relevant targets in your final response",
36564
36600
  "",
36565
36601
  `Query: ${searchQuery}`,
36566
36602
  `Search path(s): ${searchPath}`,
@@ -36613,9 +36649,12 @@ var init_vercel = __esm({
36613
36649
  }
36614
36650
  return result;
36615
36651
  };
36652
+ const previousSearches = /* @__PURE__ */ new Set();
36653
+ const paginationCounts = /* @__PURE__ */ new Map();
36654
+ const MAX_PAGES_PER_QUERY = 3;
36616
36655
  return (0, import_ai.tool)({
36617
36656
  name: "search",
36618
- description: searchDelegate ? `${searchDescription} (delegates code search to a subagent and returns extracted code blocks)` : searchDescription,
36657
+ description: searchDelegate ? searchDelegateDescription : searchDescription,
36619
36658
  inputSchema: searchSchema,
36620
36659
  execute: async ({ query: searchQuery, path: path9, allow_tests, exact, maxTokens: paramMaxTokens, language, session, nextPage }) => {
36621
36660
  const effectiveMaxTokens = paramMaxTokens || maxTokens;
@@ -36653,6 +36692,26 @@ var init_vercel = __esm({
36653
36692
  return await search(searchOptions);
36654
36693
  };
36655
36694
  if (!searchDelegate) {
36695
+ const searchKey = `${searchQuery}::${searchPath}::${exact || false}`;
36696
+ if (!nextPage) {
36697
+ if (previousSearches.has(searchKey)) {
36698
+ if (debug) {
36699
+ console.error(`[DEDUP] Blocked duplicate search: "${searchQuery}" in "${searchPath}"`);
36700
+ }
36701
+ return "DUPLICATE SEARCH BLOCKED: You already searched for this exact query in this path. Do NOT repeat the same search. If you need more results, set nextPage=true with the session ID from the previous search. Otherwise, try a genuinely different keyword, use extract to examine results you already found, or use attempt_completion if you have enough information.";
36702
+ }
36703
+ previousSearches.add(searchKey);
36704
+ paginationCounts.set(searchKey, 0);
36705
+ } else {
36706
+ const pageCount = (paginationCounts.get(searchKey) || 0) + 1;
36707
+ paginationCounts.set(searchKey, pageCount);
36708
+ if (pageCount > MAX_PAGES_PER_QUERY) {
36709
+ if (debug) {
36710
+ console.error(`[DEDUP] Blocked excessive pagination (page ${pageCount}/${MAX_PAGES_PER_QUERY}): "${searchQuery}" in "${searchPath}"`);
36711
+ }
36712
+ return `PAGINATION LIMIT REACHED: You have already retrieved ${MAX_PAGES_PER_QUERY} pages of results for this query. You have enough results \u2014 use extract to examine specific files, or use attempt_completion to return your findings.`;
36713
+ }
36714
+ }
36656
36715
  try {
36657
36716
  const result = maybeAnnotate(await runRawSearch());
36658
36717
  if (options.fileTracker && typeof result === "string") {
@@ -49524,6 +49583,7 @@ function generateSandboxGlobals(options) {
49524
49583
  executing.add(p5);
49525
49584
  results.push(p5);
49526
49585
  if (executing.size >= mapConcurrency) {
49586
+ console.error(`[map] Concurrency limit reached (${executing.size}/${mapConcurrency}), waiting for a slot...`);
49527
49587
  await Promise.race(executing);
49528
49588
  }
49529
49589
  }
@@ -108916,6 +108976,7 @@ var init_ProbeAgent = __esm({
108916
108976
  this.completionPrompt = options.completionPrompt || null;
108917
108977
  this.thinkingEffort = options.thinkingEffort || null;
108918
108978
  const effectiveAllowedTools = options.disableTools ? [] : options.allowedTools;
108979
+ this._rawAllowedTools = options.allowedTools;
108919
108980
  this.allowedTools = this._parseAllowedTools(effectiveAllowedTools);
108920
108981
  this.storageAdapter = options.storageAdapter || new InMemoryStorageAdapter();
108921
108982
  this.hooks = new HookManager();
@@ -109058,6 +109119,16 @@ var init_ProbeAgent = __esm({
109058
109119
  _filterMcpTools(mcpToolNames) {
109059
109120
  return mcpToolNames.filter((toolName) => this._isMcpToolAllowed(toolName));
109060
109121
  }
109122
+ /**
109123
+ * Check if query tool was explicitly listed in allowedTools (not via wildcard).
109124
+ * Query (ast-grep) is excluded by default because models struggle with AST pattern syntax.
109125
+ * @returns {boolean}
109126
+ * @private
109127
+ */
109128
+ _isQueryExplicitlyAllowed() {
109129
+ if (!this._rawAllowedTools) return false;
109130
+ return Array.isArray(this._rawAllowedTools) && this._rawAllowedTools.includes("query");
109131
+ }
109061
109132
  /**
109062
109133
  * Check if tracer is AppTracer (expects sessionId as first param) vs SimpleAppTracer
109063
109134
  * @returns {boolean} - True if tracer is AppTracer style (requires sessionId)
@@ -109351,7 +109422,7 @@ var init_ProbeAgent = __esm({
109351
109422
  if (wrappedTools.searchToolInstance && isToolAllowed("search")) {
109352
109423
  this.toolImplementations.search = wrappedTools.searchToolInstance;
109353
109424
  }
109354
- if (wrappedTools.queryToolInstance && isToolAllowed("query")) {
109425
+ if (wrappedTools.queryToolInstance && isToolAllowed("query") && this._isQueryExplicitlyAllowed()) {
109355
109426
  this.toolImplementations.query = wrappedTools.queryToolInstance;
109356
109427
  }
109357
109428
  if (wrappedTools.extractToolInstance && isToolAllowed("extract")) {
@@ -110290,12 +110361,13 @@ var init_ProbeAgent = __esm({
110290
110361
  const toolMap = {
110291
110362
  search: {
110292
110363
  schema: searchSchema,
110293
- description: "Search code in the repository using keyword queries with Elasticsearch syntax."
110294
- },
110295
- query: {
110296
- schema: querySchema,
110297
- description: "Search code using ast-grep structural pattern matching."
110364
+ description: this.searchDelegate ? "Search code in the repository by asking a question. Accepts natural language questions \u2014 a subagent breaks them into targeted keyword searches and returns extracted code blocks. Do NOT formulate keyword queries yourself." : "Search code in the repository using keyword queries with Elasticsearch syntax. Handles stemming, case-insensitive matching, and camelCase/snake_case splitting automatically \u2014 do NOT try keyword variations manually."
110298
110365
  },
110366
+ // query tool (ast-grep) removed from AI-facing tools — models struggle with pattern syntax
110367
+ // query: {
110368
+ // schema: querySchema,
110369
+ // description: 'Search code using ast-grep structural pattern matching.'
110370
+ // },
110299
110371
  extract: {
110300
110372
  schema: extractSchema,
110301
110373
  description: "Extract code blocks from files based on file paths and optional line numbers."
@@ -110963,25 +111035,27 @@ ${this.architectureContext.content}
110963
111035
  } else {
110964
111036
  systemPrompt += predefinedPrompts["code-explorer"] + "\n\n";
110965
111037
  }
111038
+ const searchToolDesc1 = this.searchDelegate ? '- search: Ask natural language questions to find code (e.g., "How does authentication work?"). A subagent handles keyword searches and returns extracted code blocks. Do NOT formulate keyword queries \u2014 just ask questions.' : "- search: Find code patterns using keyword queries with Elasticsearch syntax. Handles stemming and case variations automatically \u2014 do NOT try manual keyword variations.";
110966
111039
  systemPrompt += `You have access to powerful code search and analysis tools through MCP:
110967
- - search: Find code patterns using semantic search
111040
+ ${searchToolDesc1}
110968
111041
  - extract: Extract specific code sections with context
110969
- - query: Use AST patterns for structural code matching
110970
111042
  - listFiles: Browse directory contents
110971
111043
  - searchFiles: Find files by name patterns`;
110972
111044
  if (this.enableBash) {
110973
111045
  systemPrompt += `
110974
111046
  - bash: Execute bash commands for system operations`;
110975
111047
  }
110976
- const searchGuidance = this.searchDelegate ? "1. Start with search to retrieve extracted code blocks" : "1. Start with search to find relevant code patterns";
110977
- const extractGuidance = this.searchDelegate ? "2. Use extract only if you need more context or a full file" : "2. Use extract to get detailed context when needed";
111048
+ const searchGuidance1 = this.searchDelegate ? "1. Start with search \u2014 ask a question about what you want to understand. It returns extracted code blocks directly." : "1. Start with search to find relevant code patterns. One search per concept is usually enough \u2014 probe handles stemming and case variations.";
111049
+ const extractGuidance1 = this.searchDelegate ? "2. Use extract only if you need more context or a full file" : "2. Use extract to get detailed context when needed";
110978
111050
  systemPrompt += `
110979
111051
 
110980
111052
  When exploring code:
110981
- ${searchGuidance}
110982
- ${extractGuidance}
111053
+ ${searchGuidance1}
111054
+ ${extractGuidance1}
110983
111055
  3. Prefer focused, specific searches over broad queries
110984
- 4. Combine multiple tools to build complete understanding`;
111056
+ 4. Do NOT repeat the same search or try trivial keyword variations \u2014 probe handles stemming and case variations automatically
111057
+ 5. If 2-3 consecutive searches return no results for a concept, stop searching for it \u2014 the term likely does not exist in that codebase
111058
+ 6. Combine multiple tools to build complete understanding`;
110985
111059
  if (this.allowedFolders && this.allowedFolders.length > 0) {
110986
111060
  systemPrompt += `
110987
111061
 
@@ -111016,25 +111090,27 @@ Workspace: ${this.allowedFolders.join(", ")}`;
111016
111090
  } else {
111017
111091
  systemPrompt += predefinedPrompts["code-explorer"] + "\n\n";
111018
111092
  }
111093
+ const searchToolDesc2 = this.searchDelegate ? '- search: Ask natural language questions to find code (e.g., "How does authentication work?"). A subagent handles keyword searches and returns extracted code blocks. Do NOT formulate keyword queries \u2014 just ask questions.' : "- search: Find code patterns using keyword queries with Elasticsearch syntax. Handles stemming and case variations automatically \u2014 do NOT try manual keyword variations.";
111019
111094
  systemPrompt += `You have access to powerful code search and analysis tools through MCP:
111020
- - search: Find code patterns using semantic search
111095
+ ${searchToolDesc2}
111021
111096
  - extract: Extract specific code sections with context
111022
- - query: Use AST patterns for structural code matching
111023
111097
  - listFiles: Browse directory contents
111024
111098
  - searchFiles: Find files by name patterns`;
111025
111099
  if (this.enableBash) {
111026
111100
  systemPrompt += `
111027
111101
  - bash: Execute bash commands for system operations`;
111028
111102
  }
111029
- const searchGuidance = this.searchDelegate ? "1. Start with search to retrieve extracted code blocks" : "1. Start with search to find relevant code patterns";
111030
- const extractGuidance = this.searchDelegate ? "2. Use extract only if you need more context or a full file" : "2. Use extract to get detailed context when needed";
111103
+ const searchGuidance2 = this.searchDelegate ? "1. Start with search \u2014 ask a question about what you want to understand. It returns extracted code blocks directly." : "1. Start with search to find relevant code patterns. One search per concept is usually enough \u2014 probe handles stemming and case variations.";
111104
+ const extractGuidance2 = this.searchDelegate ? "2. Use extract only if you need more context or a full file" : "2. Use extract to get detailed context when needed";
111031
111105
  systemPrompt += `
111032
111106
 
111033
111107
  When exploring code:
111034
- ${searchGuidance}
111035
- ${extractGuidance}
111108
+ ${searchGuidance2}
111109
+ ${extractGuidance2}
111036
111110
  3. Prefer focused, specific searches over broad queries
111037
- 4. Combine multiple tools to build complete understanding`;
111111
+ 4. Do NOT repeat the same search or try trivial keyword variations \u2014 probe handles stemming and case variations automatically
111112
+ 5. If 2-3 consecutive searches return no results for a concept, stop searching for it \u2014 the term likely does not exist in that codebase
111113
+ 6. Combine multiple tools to build complete understanding`;
111038
111114
  if (this.allowedFolders && this.allowedFolders.length > 0) {
111039
111115
  systemPrompt += `
111040
111116
 
@@ -111085,10 +111161,10 @@ Workspace: ${this.allowedFolders.join(", ")}`;
111085
111161
  Follow these instructions carefully:
111086
111162
  1. Analyze the user's request.
111087
111163
  2. Use the available tools step-by-step to fulfill the request.
111088
- 3. You should always prefer the search tool for code-related questions.${this.searchDelegate ? " It already returns extracted code blocks; use extract only to expand context or read full files." : " Read full files only if really necessary."}
111164
+ 3. You should always prefer the search tool for code-related questions.${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."}
111089
111165
  4. Ensure to get really deep and understand the full picture before answering.
111090
111166
  5. Once the task is fully completed, use the attempt_completion tool to provide the final result.
111091
- 6. Prefer concise and focused search queries. Use specific keywords and phrases to narrow down results.${this.allowEdit ? `
111167
+ 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."}${this.allowEdit ? `
111092
111168
  7. When modifying files, choose the appropriate tool:
111093
111169
  - Use 'edit' for all code modifications:
111094
111170
  * PREFERRED: Use start_line (and optionally end_line) for line-targeted editing \u2014 this is the safest and most precise approach.${this.hashLines ? ' Use the line:hash references from extract/search output (e.g. "42:ab") for integrity verification.' : ""} Always use extract first to see line numbers${this.hashLines ? " and hashes" : ""}, then edit by line reference.