@probelabs/probe 0.6.0-rc302 → 0.6.0-rc304

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/cjs/index.cjs CHANGED
@@ -24138,6 +24138,87 @@ var init_dist3 = __esm({
24138
24138
  }
24139
24139
  });
24140
24140
 
24141
+ // src/utils/provider.js
24142
+ function createProviderInstance(config2) {
24143
+ switch (config2.provider) {
24144
+ case "anthropic":
24145
+ return (0, import_anthropic.createAnthropic)({
24146
+ apiKey: config2.apiKey,
24147
+ ...config2.baseURL && { baseURL: config2.baseURL }
24148
+ });
24149
+ case "openai":
24150
+ return (0, import_openai.createOpenAI)({
24151
+ compatibility: "strict",
24152
+ apiKey: config2.apiKey,
24153
+ ...config2.baseURL && { baseURL: config2.baseURL }
24154
+ });
24155
+ case "google":
24156
+ return (0, import_google.createGoogleGenerativeAI)({
24157
+ apiKey: config2.apiKey,
24158
+ ...config2.baseURL && { baseURL: config2.baseURL }
24159
+ });
24160
+ case "bedrock": {
24161
+ const bedrockConfig = {};
24162
+ if (config2.apiKey) {
24163
+ bedrockConfig.apiKey = config2.apiKey;
24164
+ } else if (config2.accessKeyId && config2.secretAccessKey) {
24165
+ bedrockConfig.accessKeyId = config2.accessKeyId;
24166
+ bedrockConfig.secretAccessKey = config2.secretAccessKey;
24167
+ if (config2.sessionToken) {
24168
+ bedrockConfig.sessionToken = config2.sessionToken;
24169
+ }
24170
+ }
24171
+ if (config2.region) bedrockConfig.region = config2.region;
24172
+ if (config2.baseURL) bedrockConfig.baseURL = config2.baseURL;
24173
+ return createAmazonBedrock(bedrockConfig);
24174
+ }
24175
+ default:
24176
+ throw new Error(`Unknown provider "${config2.provider}"`);
24177
+ }
24178
+ }
24179
+ function resolveApiKey(providerName) {
24180
+ switch (providerName) {
24181
+ case "anthropic":
24182
+ return process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN;
24183
+ case "openai":
24184
+ return process.env.OPENAI_API_KEY;
24185
+ case "google":
24186
+ return process.env.GOOGLE_GENERATIVE_AI_API_KEY || process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY;
24187
+ case "bedrock":
24188
+ return process.env.AWS_BEDROCK_API_KEY;
24189
+ default:
24190
+ return void 0;
24191
+ }
24192
+ }
24193
+ async function createLanguageModel(providerName, modelName) {
24194
+ if (!providerName) return null;
24195
+ const resolvedModel = modelName || DEFAULT_MODELS[providerName];
24196
+ if (!resolvedModel) return null;
24197
+ try {
24198
+ const apiKey = resolveApiKey(providerName);
24199
+ const provider = createProviderInstance({ provider: providerName, ...apiKey ? { apiKey } : {} });
24200
+ return provider(resolvedModel);
24201
+ } catch {
24202
+ return null;
24203
+ }
24204
+ }
24205
+ var import_anthropic, import_openai, import_google, DEFAULT_MODELS;
24206
+ var init_provider = __esm({
24207
+ "src/utils/provider.js"() {
24208
+ "use strict";
24209
+ import_anthropic = require("@ai-sdk/anthropic");
24210
+ import_openai = require("@ai-sdk/openai");
24211
+ import_google = require("@ai-sdk/google");
24212
+ init_dist3();
24213
+ DEFAULT_MODELS = {
24214
+ anthropic: "claude-sonnet-4-6",
24215
+ openai: "gpt-5.2",
24216
+ google: "gemini-2.5-flash",
24217
+ bedrock: "anthropic.claude-sonnet-4-6"
24218
+ };
24219
+ }
24220
+ });
24221
+
24141
24222
  // node_modules/gpt-tokenizer/esm/bpeRanks/o200k_base.js
24142
24223
  var c0, c1, bpe, o200k_base_default;
24143
24224
  var init_o200k_base = __esm({
@@ -25993,6 +26074,13 @@ var init_otelLogBridge = __esm({
25993
26074
  });
25994
26075
 
25995
26076
  // src/agent/simpleTelemetry.js
26077
+ var simpleTelemetry_exports = {};
26078
+ __export(simpleTelemetry_exports, {
26079
+ SimpleAppTracer: () => SimpleAppTracer,
26080
+ SimpleTelemetry: () => SimpleTelemetry,
26081
+ initializeSimpleTelemetryFromOptions: () => initializeSimpleTelemetryFromOptions,
26082
+ truncateForSpan: () => truncateForSpan
26083
+ });
25996
26084
  function truncateForSpan(text, maxLen = 4096) {
25997
26085
  if (!text || text.length <= maxLen) return text || "";
25998
26086
  const half = Math.floor((maxLen - 40) / 2);
@@ -27718,12 +27806,16 @@ function resolveTargetPath(target, cwd) {
27718
27806
  }
27719
27807
  return filePart + suffix;
27720
27808
  }
27721
- var import_path6, searchSchema, searchAllSchema, querySchema, extractSchema, delegateSchema, listSkillsSchema, useSkillSchema, listFilesSchema, searchFilesSchema, readImageSchema, bashSchema, analyzeAllSchema, executePlanSchema, cleanupExecutePlanSchema, searchDescription, searchDelegateDescription, queryDescription, extractDescription, delegateDescription, bashDescription, analyzeAllDescription;
27809
+ var import_path6, searchDelegateSchema, searchSchema, searchAllSchema, querySchema, extractSchema, delegateSchema, listSkillsSchema, useSkillSchema, listFilesSchema, searchFilesSchema, readImageSchema, bashSchema, analyzeAllSchema, executePlanSchema, cleanupExecutePlanSchema, searchDescription, searchDelegateDescription, queryDescription, extractDescription, delegateDescription, bashDescription, analyzeAllDescription;
27722
27810
  var init_common = __esm({
27723
27811
  "src/tools/common.js"() {
27724
27812
  "use strict";
27725
27813
  init_zod();
27726
27814
  import_path6 = require("path");
27815
+ searchDelegateSchema = external_exports2.object({
27816
+ query: external_exports2.string().describe('Natural language question about the code (e.g., "How does authentication work?", "Where is the rate limiting middleware?"). Do NOT use keyword syntax \u2014 just describe what you are looking for in plain English. A subagent will handle keyword searches for you.'),
27817
+ path: external_exports2.string().optional().default(".").describe("Path to search in.")
27818
+ });
27727
27819
  searchSchema = external_exports2.object({
27728
27820
  query: external_exports2.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."),
27729
27821
  path: external_exports2.string().optional().default(".").describe('Path to search in. For dependencies use "go:github.com/owner/repo", "js:package_name", or "rust:cargo_name" etc.'),
@@ -27789,7 +27881,17 @@ var init_common = __esm({
27789
27881
  clearSessionStore: external_exports2.boolean().optional().default(false).describe("Clear the session store (persisted data across execute_plan calls)")
27790
27882
  });
27791
27883
  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.';
27792
- 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.';
27884
+ searchDelegateDescription = `Find where relevant code is located by asking a natural language question. A subagent searches the codebase and returns file locations grouped by relevance, with reasons explaining why each group matters. Use extract() to read the actual code from the returned locations.
27885
+
27886
+ Returns JSON: { "confidence": "high|medium|low", "groups": [{ "reason": "why these files matter", "files": ["path#Symbol", ...] }] }
27887
+
27888
+ IMPORTANT \u2014 each call spawns a subagent (expensive, takes minutes). Be deliberate:
27889
+ - Ask plain English questions about WHERE code is, NOT keyword queries. Good: "How are user sessions extracted from cookies?" Bad: "ctxGetSession OR GetSession"
27890
+ - Each call should explore a DIFFERENT ANGLE of the problem. Don't rephrase \u2014 reframe:
27891
+ Good: 1) "How are sessions extracted from HTTP requests?" 2) "What middleware runs before route handlers?" 3) "How is the session cookie parsed and validated?"
27892
+ Bad: 1) "How does session extraction work?" 2) "Where is the session extracted?" 3) "Find session extraction code" \u2190 same question reworded
27893
+ - If a search returned no useful results, ask about a DIFFERENT part of the system. Think: what upstream/downstream component touches this?
27894
+ - After getting results, use extract() to read the files you need \u2014 search only locates, extract reads.`;
27793
27895
  queryDescription = "Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.";
27794
27896
  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.";
27795
27897
  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.";
@@ -83019,14 +83121,11 @@ function buildFallbackProvidersFromEnv(options = {}) {
83019
83121
  }
83020
83122
  return providers;
83021
83123
  }
83022
- var import_anthropic, import_openai, import_google, FALLBACK_STRATEGIES, DEFAULT_MODELS, FallbackManager;
83124
+ var FALLBACK_STRATEGIES, DEFAULT_MODELS2, FallbackManager;
83023
83125
  var init_FallbackManager = __esm({
83024
83126
  "src/agent/FallbackManager.js"() {
83025
83127
  "use strict";
83026
- import_anthropic = require("@ai-sdk/anthropic");
83027
- import_openai = require("@ai-sdk/openai");
83028
- import_google = require("@ai-sdk/google");
83029
- init_dist3();
83128
+ init_provider();
83030
83129
  FALLBACK_STRATEGIES = {
83031
83130
  SAME_MODEL: "same-model",
83032
83131
  // Try same model on different providers
@@ -83037,12 +83136,7 @@ var init_FallbackManager = __esm({
83037
83136
  CUSTOM: "custom"
83038
83137
  // Use custom provider list
83039
83138
  };
83040
- DEFAULT_MODELS = {
83041
- anthropic: "claude-sonnet-4-6",
83042
- openai: "gpt-5.2",
83043
- google: "gemini-2.5-flash",
83044
- bedrock: "anthropic.claude-sonnet-4-6"
83045
- };
83139
+ DEFAULT_MODELS2 = DEFAULT_MODELS;
83046
83140
  FallbackManager = class {
83047
83141
  /**
83048
83142
  * Create a new FallbackManager
@@ -83115,45 +83209,7 @@ var init_FallbackManager = __esm({
83115
83209
  */
83116
83210
  _createProviderInstance(config2) {
83117
83211
  try {
83118
- switch (config2.provider) {
83119
- case "anthropic":
83120
- return (0, import_anthropic.createAnthropic)({
83121
- apiKey: config2.apiKey,
83122
- ...config2.baseURL && { baseURL: config2.baseURL }
83123
- });
83124
- case "openai":
83125
- return (0, import_openai.createOpenAI)({
83126
- compatibility: "strict",
83127
- apiKey: config2.apiKey,
83128
- ...config2.baseURL && { baseURL: config2.baseURL }
83129
- });
83130
- case "google":
83131
- return (0, import_google.createGoogleGenerativeAI)({
83132
- apiKey: config2.apiKey,
83133
- ...config2.baseURL && { baseURL: config2.baseURL }
83134
- });
83135
- case "bedrock": {
83136
- const bedrockConfig = {};
83137
- if (config2.apiKey) {
83138
- bedrockConfig.apiKey = config2.apiKey;
83139
- } else if (config2.accessKeyId && config2.secretAccessKey) {
83140
- bedrockConfig.accessKeyId = config2.accessKeyId;
83141
- bedrockConfig.secretAccessKey = config2.secretAccessKey;
83142
- if (config2.sessionToken) {
83143
- bedrockConfig.sessionToken = config2.sessionToken;
83144
- }
83145
- }
83146
- if (config2.region) {
83147
- bedrockConfig.region = config2.region;
83148
- }
83149
- if (config2.baseURL) {
83150
- bedrockConfig.baseURL = config2.baseURL;
83151
- }
83152
- return createAmazonBedrock(bedrockConfig);
83153
- }
83154
- default:
83155
- throw new Error(`FallbackManager: Unknown provider "${config2.provider}"`);
83156
- }
83212
+ return createProviderInstance(config2);
83157
83213
  } catch (error40) {
83158
83214
  const providerName = this._getProviderDisplayName(config2);
83159
83215
  throw new Error(`Failed to create provider instance for ${providerName}: ${error40.message}`);
@@ -83166,7 +83222,7 @@ var init_FallbackManager = __esm({
83166
83222
  * @private
83167
83223
  */
83168
83224
  _getModelName(config2) {
83169
- return config2.model || DEFAULT_MODELS[config2.provider];
83225
+ return config2.model || DEFAULT_MODELS2[config2.provider];
83170
83226
  }
83171
83227
  /**
83172
83228
  * Get provider display name for logging
@@ -97429,15 +97485,12 @@ function debugLogToolResults(toolResults) {
97429
97485
  console.log(`[DEBUG] tool: ${tr.toolName} | args: ${debugTruncate(argsStr)} | result: ${debugTruncate(resultStr)}`);
97430
97486
  }
97431
97487
  }
97432
- var import_dotenv, import_anthropic2, import_openai2, import_google2, import_ai4, import_crypto8, import_events4, import_fs11, import_promises6, import_path16, ENGINE_ACTIVITY_TIMEOUT_DEFAULT, ENGINE_ACTIVITY_TIMEOUT_MIN, ENGINE_ACTIVITY_TIMEOUT_MAX, MAX_TOOL_ITERATIONS, MAX_HISTORY_MESSAGES, MAX_IMAGE_FILE_SIZE, ProbeAgent;
97488
+ var import_dotenv, import_ai4, import_crypto8, import_events4, import_fs11, import_promises6, import_path16, ENGINE_ACTIVITY_TIMEOUT_DEFAULT, ENGINE_ACTIVITY_TIMEOUT_MIN, ENGINE_ACTIVITY_TIMEOUT_MAX, MAX_TOOL_ITERATIONS, MAX_HISTORY_MESSAGES, MAX_IMAGE_FILE_SIZE, ProbeAgent;
97433
97489
  var init_ProbeAgent = __esm({
97434
97490
  "src/agent/ProbeAgent.js"() {
97435
97491
  "use strict";
97436
97492
  import_dotenv = __toESM(require_main(), 1);
97437
- import_anthropic2 = require("@ai-sdk/anthropic");
97438
- import_openai2 = require("@ai-sdk/openai");
97439
- import_google2 = require("@ai-sdk/google");
97440
- init_dist3();
97493
+ init_provider();
97441
97494
  import_ai4 = require("ai");
97442
97495
  import_crypto8 = require("crypto");
97443
97496
  import_events4 = require("events");
@@ -98676,11 +98729,8 @@ var init_ProbeAgent = __esm({
98676
98729
  * Initialize Anthropic model
98677
98730
  */
98678
98731
  initializeAnthropicModel(apiKey, apiUrl, modelName) {
98679
- this.provider = (0, import_anthropic2.createAnthropic)({
98680
- apiKey,
98681
- ...apiUrl && { baseURL: apiUrl }
98682
- });
98683
- this.model = modelName || "claude-sonnet-4-6";
98732
+ this.provider = createProviderInstance({ provider: "anthropic", apiKey, ...apiUrl && { baseURL: apiUrl } });
98733
+ this.model = modelName || DEFAULT_MODELS.anthropic;
98684
98734
  this.apiType = "anthropic";
98685
98735
  if (this.debug) {
98686
98736
  console.log(`Using Anthropic API with model: ${this.model}${apiUrl ? ` (URL: ${apiUrl})` : ""}`);
@@ -98690,12 +98740,8 @@ var init_ProbeAgent = __esm({
98690
98740
  * Initialize OpenAI model
98691
98741
  */
98692
98742
  initializeOpenAIModel(apiKey, apiUrl, modelName) {
98693
- this.provider = (0, import_openai2.createOpenAI)({
98694
- compatibility: "strict",
98695
- apiKey,
98696
- ...apiUrl && { baseURL: apiUrl }
98697
- });
98698
- this.model = modelName || "gpt-5.2";
98743
+ this.provider = createProviderInstance({ provider: "openai", apiKey, ...apiUrl && { baseURL: apiUrl } });
98744
+ this.model = modelName || DEFAULT_MODELS.openai;
98699
98745
  this.apiType = "openai";
98700
98746
  if (this.debug) {
98701
98747
  console.log(`Using OpenAI API with model: ${this.model}${apiUrl ? ` (URL: ${apiUrl})` : ""}`);
@@ -98705,10 +98751,7 @@ var init_ProbeAgent = __esm({
98705
98751
  * Initialize Google model
98706
98752
  */
98707
98753
  initializeGoogleModel(apiKey, apiUrl, modelName) {
98708
- this.provider = (0, import_google2.createGoogleGenerativeAI)({
98709
- apiKey,
98710
- ...apiUrl && { baseURL: apiUrl }
98711
- });
98754
+ this.provider = createProviderInstance({ provider: "google", apiKey, ...apiUrl && { baseURL: apiUrl } });
98712
98755
  this.model = modelName || "gemini-2.5-pro";
98713
98756
  this.apiType = "google";
98714
98757
  if (this.debug) {
@@ -99152,24 +99195,16 @@ var init_ProbeAgent = __esm({
99152
99195
  * Initialize AWS Bedrock model
99153
99196
  */
99154
99197
  initializeBedrockModel(accessKeyId, secretAccessKey, region, sessionToken, apiKey, baseURL, modelName) {
99155
- const config2 = {};
99156
- if (apiKey) {
99157
- config2.apiKey = apiKey;
99158
- } else if (accessKeyId && secretAccessKey) {
99159
- config2.accessKeyId = accessKeyId;
99160
- config2.secretAccessKey = secretAccessKey;
99161
- if (sessionToken) {
99162
- config2.sessionToken = sessionToken;
99163
- }
99164
- }
99165
- if (region) {
99166
- config2.region = region;
99167
- }
99168
- if (baseURL) {
99169
- config2.baseURL = baseURL;
99170
- }
99171
- this.provider = createAmazonBedrock(config2);
99172
- this.model = modelName || "anthropic.claude-sonnet-4-6";
99198
+ this.provider = createProviderInstance({
99199
+ provider: "bedrock",
99200
+ apiKey,
99201
+ accessKeyId,
99202
+ secretAccessKey,
99203
+ sessionToken,
99204
+ region,
99205
+ baseURL
99206
+ });
99207
+ this.model = modelName || DEFAULT_MODELS.bedrock;
99173
99208
  this.apiType = "bedrock";
99174
99209
  if (this.debug) {
99175
99210
  const authMethod = apiKey ? "API Key" : "AWS Credentials";
@@ -99752,7 +99787,7 @@ ${this.architectureContext.content}
99752
99787
  } else {
99753
99788
  systemPrompt += predefinedPrompts["code-explorer"] + "\n\n";
99754
99789
  }
99755
- 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.";
99790
+ const searchToolDesc1 = this.searchDelegate ? '- search: Ask natural language questions to find code locations (e.g., "How does authentication work?"). Returns structured JSON with file locations grouped by relevance. Use extract() on the returned files to read the actual code. 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.";
99756
99791
  systemPrompt += `You have access to powerful code search and analysis tools through MCP:
99757
99792
  ${searchToolDesc1}
99758
99793
  - extract: Extract specific code sections with context
@@ -99762,8 +99797,8 @@ ${searchToolDesc1}
99762
99797
  systemPrompt += `
99763
99798
  - bash: Execute bash commands for system operations (building, running tests, git, etc.). NEVER use bash for code exploration (no grep, cat, find, head, tail) \u2014 always use search and extract tools instead, they are faster and more accurate.`;
99764
99799
  }
99765
- 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.";
99766
- 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";
99800
+ const searchGuidance1 = this.searchDelegate ? "1. Start with search \u2014 ask a question about what you want to understand. It returns file locations grouped by relevance (JSON with confidence and groups)." : "1. Start with search to find relevant code patterns. One search per concept is usually enough \u2014 probe handles stemming and case variations.";
99801
+ const extractGuidance1 = this.searchDelegate ? '2. Use extract on the file locations returned by search to read the actual code. Each group has a "reason" explaining why those files matter.' : "2. Use extract to get detailed context when needed";
99767
99802
  systemPrompt += `
99768
99803
 
99769
99804
  When exploring code:
@@ -99807,7 +99842,7 @@ Workspace: ${this.allowedFolders.join(", ")}`;
99807
99842
  } else {
99808
99843
  systemPrompt += predefinedPrompts["code-explorer"] + "\n\n";
99809
99844
  }
99810
- 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.";
99845
+ const searchToolDesc2 = this.searchDelegate ? '- search: Ask natural language questions to find code locations (e.g., "How does authentication work?"). Returns structured JSON with file locations grouped by relevance. Use extract() on the returned files to read the actual code. 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.";
99811
99846
  systemPrompt += `You have access to powerful code search and analysis tools through MCP:
99812
99847
  ${searchToolDesc2}
99813
99848
  - extract: Extract specific code sections with context
@@ -99817,8 +99852,8 @@ ${searchToolDesc2}
99817
99852
  systemPrompt += `
99818
99853
  - bash: Execute bash commands for system operations (building, running tests, git, etc.). NEVER use bash for code exploration (no grep, cat, find, head, tail) \u2014 always use search and extract tools instead, they are faster and more accurate.`;
99819
99854
  }
99820
- 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.";
99821
- 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";
99855
+ const searchGuidance2 = this.searchDelegate ? "1. Start with search \u2014 ask a question about what you want to understand. It returns file locations grouped by relevance (JSON with confidence and groups)." : "1. Start with search to find relevant code patterns. One search per concept is usually enough \u2014 probe handles stemming and case variations.";
99856
+ const extractGuidance2 = this.searchDelegate ? '2. Use extract on the file locations returned by search to read the actual code. Each group has a "reason" explaining why those files matter.' : "2. Use extract to get detailed context when needed";
99822
99857
  systemPrompt += `
99823
99858
 
99824
99859
  When exploring code:
@@ -99878,10 +99913,10 @@ Workspace: ${this.allowedFolders.join(", ")}`;
99878
99913
  Follow these instructions carefully:
99879
99914
  1. Analyze the user's request.
99880
99915
  2. Use the available tools step-by-step to fulfill the request.
99881
- 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."}
99916
+ 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 file locations grouped by relevance. Then use extract() on those locations to read the actual code." : " Search handles stemming and case variations automatically \u2014 do NOT try keyword variations manually. Read full files only if really necessary."}
99882
99917
  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).
99883
99918
  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.
99884
- 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."}
99919
+ 6. ${this.searchDelegate ? 'Ask clear, specific questions when searching. Each search should target a distinct concept or question. NEVER re-search the same concept with different phrasing \u2014 if you already searched for "wrapToolWithEmitter", do NOT search again for "definition of wrapToolWithEmitter" or "how wrapToolWithEmitter works". Use extract() on the files already found instead. Limit yourself to one search per distinct concept. When formulating queries, describe WHAT you are looking for, not WHERE \u2014 the search agent will search the full codebase. Do NOT include file names or class names in the query unless that IS the concept (e.g., say "search dedup logic" not "search dedup ProbeAgent").' : "Prefer concise and focused search queries. Use specific keywords and phrases to narrow down results."}
99885
99920
  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 ? `
99886
99921
  7. When modifying files, choose the appropriate tool:
99887
99922
  - Use 'edit' for all code modifications:
@@ -100608,9 +100643,11 @@ Provide your BEST answer NOW using the information you have already gathered. Do
100608
100643
  const searchesTried = _toolCallLog.filter((tc) => tc.name === "search").map((tc) => `"${tc.args.query || ""}"${tc.args.exact ? " (exact)" : ""}`).filter((v, i, a) => a.indexOf(v) === i);
100609
100644
  const searchSummary = searchesTried.length > 0 ? `
100610
100645
  Searches attempted: ${searchesTried.join(", ")}` : "";
100646
+ const isCodeSearcher = this.promptType === "code-searcher";
100647
+ const lastIterMessage = isCodeSearcher ? `\u26A0\uFE0F LAST ITERATION \u2014 you are out of tool calls. Output your JSON response NOW with whatever files you have verified so far. Set confidence to "low" if your search was incomplete. Include the "searches" array listing all search queries you made with their paths and outcomes.${searchSummary}` : `\u26A0\uFE0F LAST ITERATION \u2014 you are out of tool calls. Provide your BEST answer NOW with the information gathered so far. If you could not find what was requested, explain exactly what you searched for and why it did not work, so the caller can try a different approach.${searchSummary}`;
100611
100648
  return {
100612
100649
  toolChoice: "none",
100613
- userMessage: `\u26A0\uFE0F LAST ITERATION \u2014 you are out of tool calls. Provide your BEST answer NOW with the information gathered so far. If you could not find what was requested, explain exactly what you searched for and why it did not work, so the caller can try a different approach.${searchSummary}`
100650
+ userMessage: lastIterMessage
100614
100651
  };
100615
100652
  }
100616
100653
  if (steps.length >= 2) {
@@ -101145,29 +101182,41 @@ Be thorough \u2014 this is the user's only response. Include all useful informat
101145
101182
  if (!finalResult || finalResult === DEFAULT_MAX_ITER_MSG) {
101146
101183
  try {
101147
101184
  const searchQueries = [];
101185
+ const searchDetails = [];
101148
101186
  const toolCounts = {};
101149
101187
  for (const tc of _toolCallLog) {
101150
101188
  toolCounts[tc.name] = (toolCounts[tc.name] || 0) + 1;
101151
101189
  if (tc.name === "search") {
101152
101190
  const q = tc.args.query || "";
101191
+ const p = tc.args.path || ".";
101153
101192
  const exact = tc.args.exact ? " (exact)" : "";
101154
101193
  searchQueries.push(`"${q}"${exact}`);
101194
+ searchDetails.push({ query: q, path: p, had_results: false });
101155
101195
  }
101156
101196
  }
101157
101197
  const toolBreakdown = Object.entries(toolCounts).map(([name15, count]) => `${name15}: ${count}x`).join(", ");
101158
101198
  const uniqueSearches = [...new Set(searchQueries)];
101159
- let summary = `I was unable to complete your request after ${currentIteration} tool iterations.
101199
+ if (this.promptType === "code-searcher") {
101200
+ finalResult = JSON.stringify({
101201
+ confidence: "low",
101202
+ reason: "Search incomplete \u2014 iteration limit reached",
101203
+ groups: [],
101204
+ searches: searchDetails
101205
+ });
101206
+ } else {
101207
+ let summary = `I was unable to complete your request after ${currentIteration} tool iterations.
101160
101208
 
101161
101209
  `;
101162
- summary += `Tool calls made: ${toolBreakdown || "none"}
101210
+ summary += `Tool calls made: ${toolBreakdown || "none"}
101163
101211
  `;
101164
- if (uniqueSearches.length > 0) {
101165
- summary += `Search queries tried: ${uniqueSearches.join(", ")}
101212
+ if (uniqueSearches.length > 0) {
101213
+ summary += `Search queries tried: ${uniqueSearches.join(", ")}
101166
101214
  `;
101167
- }
101168
- summary += `
101215
+ }
101216
+ summary += `
101169
101217
  The search approach may be fundamentally wrong for this query. Consider: using exact=true for literal string matching, using bash/grep for pattern-based file searches, or trying a completely different strategy instead of repeating similar searches.`;
101170
- finalResult = summary;
101218
+ finalResult = summary;
101219
+ }
101171
101220
  } catch {
101172
101221
  finalResult = DEFAULT_MAX_ITER_MSG;
101173
101222
  }
@@ -102008,6 +102057,7 @@ async function delegate({
102008
102057
  });
102009
102058
  let parentAbortHandler;
102010
102059
  let parentAbortHardCancelId = null;
102060
+ let raceSettled = false;
102011
102061
  const parentAbortPromise = new Promise((_, reject2) => {
102012
102062
  if (parentAbortSignal) {
102013
102063
  if (parentAbortSignal.aborted) {
@@ -102016,6 +102066,7 @@ async function delegate({
102016
102066
  return;
102017
102067
  }
102018
102068
  parentAbortHandler = () => {
102069
+ if (raceSettled) return;
102019
102070
  subagent.triggerGracefulWindDown();
102020
102071
  if (debug) {
102021
102072
  console.error(`[DELEGATE] Parent abort signal received \u2014 triggered graceful wind-down on subagent ${sessionId}`);
@@ -102028,6 +102079,7 @@ async function delegate({
102028
102079
  });
102029
102080
  }
102030
102081
  parentAbortHardCancelId = setTimeout(() => {
102082
+ if (raceSettled) return;
102031
102083
  if (debug) {
102032
102084
  console.error(`[DELEGATE] Graceful wind-down deadline expired \u2014 hard cancelling subagent ${sessionId}`);
102033
102085
  }
@@ -102053,6 +102105,7 @@ async function delegate({
102053
102105
  try {
102054
102106
  response = await Promise.race(racers);
102055
102107
  } finally {
102108
+ raceSettled = true;
102056
102109
  if (parentAbortHandler && parentAbortSignal) {
102057
102110
  parentAbortSignal.removeEventListener("abort", parentAbortHandler);
102058
102111
  }
@@ -102093,10 +102146,12 @@ async function delegate({
102093
102146
  "delegation.success": true
102094
102147
  });
102095
102148
  if (delegationSpan) {
102149
+ const { truncateForSpan: truncateForSpan2 } = await Promise.resolve().then(() => (init_simpleTelemetry(), simpleTelemetry_exports));
102096
102150
  delegationSpan.setAttributes({
102097
102151
  "delegation.result.success": true,
102098
102152
  "delegation.result.response_length": response.length,
102099
- "delegation.result.duration_ms": duration3
102153
+ "delegation.result.duration_ms": duration3,
102154
+ "delegation.result": truncateForSpan2(response, 4096)
102100
102155
  });
102101
102156
  delegationSpan.setStatus({ code: 1 });
102102
102157
  delegationSpan.end();
@@ -102149,9 +102204,13 @@ var init_delegate = __esm({
102149
102204
  init_ProbeAgent();
102150
102205
  DelegationManager = class {
102151
102206
  constructor(options = {}) {
102152
- this.maxConcurrent = options.maxConcurrent ?? parseInt(process.env.MAX_CONCURRENT_DELEGATIONS || "3", 10);
102153
- this.maxPerSession = options.maxPerSession ?? parseInt(process.env.MAX_DELEGATIONS_PER_SESSION || "10", 10);
102154
- this.defaultQueueTimeout = options.queueTimeout ?? parseInt(process.env.DELEGATION_QUEUE_TIMEOUT || "60000", 10);
102207
+ const parseSafe = (val, fallback) => {
102208
+ const n = parseInt(val, 10);
102209
+ return Number.isNaN(n) ? fallback : n;
102210
+ };
102211
+ this.maxConcurrent = options.maxConcurrent ?? parseSafe(process.env.MAX_CONCURRENT_DELEGATIONS, 3);
102212
+ this.maxPerSession = options.maxPerSession ?? parseSafe(process.env.MAX_DELEGATIONS_PER_SESSION, 10);
102213
+ this.defaultQueueTimeout = options.queueTimeout ?? parseSafe(process.env.DELEGATION_QUEUE_TIMEOUT, 6e4);
102155
102214
  this.sessionDelegations = /* @__PURE__ */ new Map();
102156
102215
  this.globalActive = 0;
102157
102216
  this.waitQueue = [];
@@ -102962,6 +103021,72 @@ function autoQuoteSearchTerms(query2) {
102962
103021
  });
102963
103022
  return result.join(" ");
102964
103023
  }
103024
+ async function checkDelegateDedup(newQuery, previousQueries, model, debug) {
103025
+ if (!model || previousQueries.length === 0) {
103026
+ return { action: "allow", reason: "no previous queries" };
103027
+ }
103028
+ const previousList = previousQueries.map((q, i) => {
103029
+ let line = `${i + 1}. "${q.query}" (path: ${q.path}, found results: ${q.hadResults})`;
103030
+ if (q.reason) line += `
103031
+ Outcome: ${q.reason}`;
103032
+ if (q.groups && q.groups.length > 0) {
103033
+ line += `
103034
+ Found: ${q.groups.map((g) => g.reason).join("; ")}`;
103035
+ }
103036
+ return line;
103037
+ }).join("\n");
103038
+ try {
103039
+ const result = await (0, import_ai5.generateText)({
103040
+ model,
103041
+ maxTokens: 150,
103042
+ temperature: 0,
103043
+ prompt: `You decide if a code search query is redundant given previous queries in the same session.
103044
+
103045
+ PREVIOUS QUERIES:
103046
+ ${previousList}
103047
+
103048
+ NEW QUERY: "${newQuery}"
103049
+
103050
+ Respond with exactly one line: ACTION|REASON
103051
+ For rewrites: rewrite|REASON|REWRITTEN_QUERY
103052
+
103053
+ BLOCK when:
103054
+ - Same concept, different phrasing: "find X" / "definition of X" / "where is X" / "X implementation" \u2192 all the same
103055
+ - Synonym or narrower term of a previous query: "dedup" \u2192 "duplicate" \u2192 "unique" \u2192 all the same concept
103056
+ - Single generic word that's just a synonym of a previous failed query
103057
+ - Query is trying to brute-force the same concept with different keywords after previous failures
103058
+
103059
+ REWRITE when:
103060
+ - Previous query was too narrow and failed, new query targets the same goal but could use a FUNDAMENTALLY different search strategy (e.g. searching for a caller instead of the function name, or searching the config/registration site instead of the implementation)
103061
+ - Previous query found WRONG results (e.g. found "FallbackManager" when looking for "dedup logic") \u2014 rewrite to target the actual concept more precisely using implementation-level terms
103062
+
103063
+ ALLOW only when:
103064
+ - The new query targets a COMPLETELY DIFFERENT feature, module, or subsystem \u2014 not just a different word for the same thing
103065
+
103066
+ Only BLOCK when you are CERTAIN the queries target the same concept. When uncertain, ALLOW \u2014 a missed dedup is cheaper than blocking a valid search.
103067
+
103068
+ Examples:
103069
+ - Prev: "wrapToolWithEmitter" \u2192 New: "definition of wrapToolWithEmitter" \u2192 block|Same symbol
103070
+ - Prev: "search dedup" (no results) \u2192 New: "dedup" \u2192 block|Synonym of failed query
103071
+ - Prev: "dedup" (no results) \u2192 New: "duplicate" \u2192 block|Synonym of failed query
103072
+ - Prev: "dedup" (no results) \u2192 New: "unique" \u2192 block|Synonym of failed query
103073
+ - Prev: "auth middleware" \u2192 New: "rate limiting" \u2192 allow|Different subsystem
103074
+ - Prev: "search dedup" (no results) \u2192 New: "previousSearches Map" \u2192 rewrite|Searching for implementation detail instead of concept|previousSearches OR searchKey`
103075
+ });
103076
+ const line = result.text.trim().split("\n")[0];
103077
+ const parts = line.split("|");
103078
+ const action = (parts[0] || "").toLowerCase().trim();
103079
+ if (action === "block") {
103080
+ return { action: "block", reason: parts[1]?.trim() || "duplicate query" };
103081
+ } else if (action === "rewrite" && parts[2]) {
103082
+ return { action: "rewrite", reason: parts[1]?.trim() || "refined query", rewritten: parts[2].trim() };
103083
+ }
103084
+ return { action: "allow", reason: parts[1]?.trim() || "new concept" };
103085
+ } catch (err) {
103086
+ if (debug) console.error("[DEDUP-LLM] Error:", err.message);
103087
+ return { action: "allow", reason: "dedup check failed, allowing" };
103088
+ }
103089
+ }
102965
103090
  function normalizeTargets(targets) {
102966
103091
  if (!Array.isArray(targets)) return [];
102967
103092
  const seen = /* @__PURE__ */ new Set();
@@ -103024,8 +103149,8 @@ function fallbackTargetsFromText(text) {
103024
103149
  }
103025
103150
  return candidates;
103026
103151
  }
103027
- function parseDelegatedTargets(rawResponse) {
103028
- if (!rawResponse || typeof rawResponse !== "string") return [];
103152
+ function parseDelegatedResponse(rawResponse) {
103153
+ if (!rawResponse || typeof rawResponse !== "string") return null;
103029
103154
  const trimmed = rawResponse.trim();
103030
103155
  const tryParse = (text) => {
103031
103156
  try {
@@ -103042,14 +103167,37 @@ function parseDelegatedTargets(rawResponse) {
103042
103167
  }
103043
103168
  }
103044
103169
  if (parsed) {
103045
- if (Array.isArray(parsed)) {
103046
- return normalizeTargets(parsed);
103170
+ if (Array.isArray(parsed.groups)) {
103171
+ return {
103172
+ confidence: parsed.confidence || "medium",
103173
+ reason: parsed.reason || "",
103174
+ groups: parsed.groups.map((g) => ({
103175
+ reason: g.reason || "",
103176
+ files: normalizeTargets(g.files || [])
103177
+ })).filter((g) => g.files.length > 0),
103178
+ searches: Array.isArray(parsed.searches) ? parsed.searches : []
103179
+ };
103047
103180
  }
103048
103181
  if (Array.isArray(parsed.targets)) {
103049
- return normalizeTargets(parsed.targets);
103182
+ const files2 = normalizeTargets(parsed.targets);
103183
+ if (files2.length > 0) {
103184
+ return { confidence: "medium", reason: "", groups: [{ reason: "Search results", files: files2 }], searches: [] };
103185
+ }
103186
+ return null;
103050
103187
  }
103188
+ if (Array.isArray(parsed)) {
103189
+ const files2 = normalizeTargets(parsed);
103190
+ if (files2.length > 0) {
103191
+ return { confidence: "medium", reason: "", groups: [{ reason: "Search results", files: files2 }], searches: [] };
103192
+ }
103193
+ return null;
103194
+ }
103195
+ }
103196
+ const files = normalizeTargets(fallbackTargetsFromText(trimmed));
103197
+ if (files.length > 0) {
103198
+ return { confidence: "low", reason: "", groups: [{ reason: "Search results", files }], searches: [] };
103051
103199
  }
103052
- return normalizeTargets(fallbackTargetsFromText(trimmed));
103200
+ return null;
103053
103201
  }
103054
103202
  function splitTargetSuffix(target) {
103055
103203
  const searchStart = target.length > 2 && target[1] === ":" && /[a-zA-Z]/.test(target[0]) ? 2 : 0;
@@ -103063,129 +103211,78 @@ function splitTargetSuffix(target) {
103063
103211
  return { filePart: target, suffix: "" };
103064
103212
  }
103065
103213
  function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, allowTests }) {
103066
- return [
103067
- "You are a code-search subagent. Your job is to find ALL relevant code locations for the given query.",
103068
- "",
103069
- "The query may be complex - it could be a natural language question, a multi-part request, or a simple keyword.",
103070
- "Break down complex queries into multiple searches to cover all aspects.",
103071
- "",
103072
- "Available tools:",
103073
- "- 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.",
103074
- "- extract: Verify code snippets to ensure targets are actually relevant before including them.",
103075
- "- listFiles: Understand directory structure to find where relevant code might live.",
103076
- "",
103077
- "CRITICAL - How probe search works (do NOT ignore):",
103078
- "- By default (exact=false), probe ALREADY handles stemming, case-insensitive matching, and camelCase/snake_case splitting automatically.",
103079
- '- Searching "allowed_ips" ALREADY matches "AllowedIPs", "allowedIps", "allowed_ips", etc. Do NOT manually try case/style variations.',
103080
- '- Searching "getUserData" ALREADY matches "get", "user", "data" and their variations.',
103081
- "- NEVER repeat the same search query \u2014 you will get the same results. Changing the path does NOT change this.",
103082
- "- NEVER search trivial variations of the same keyword (e.g., AllowedIPs then allowedIps then allowed_ips). This is wasteful \u2014 probe handles it.",
103083
- "- If a search returns no results, the term likely does not exist. Try a genuinely DIFFERENT keyword or concept, not a variation.",
103084
- "- If 2-3 searches return no results for a concept, STOP searching for it and move on. Do NOT keep retrying.",
103085
- "",
103086
- "When to use exact=true:",
103087
- "- Use exact=true when searching for a KNOWN symbol name (function, type, variable, struct).",
103088
- "- exact=true matches the literal string only \u2014 no stemming, no splitting.",
103089
- '- This is ideal for precise lookups: exact=true "ForwardMessage", exact=true "SessionLimiter", exact=true "ThrottleRetryLimit".',
103090
- "- IMPORTANT: Use exact=true when searching for strings containing punctuation, quotes, or empty values.",
103091
- " Default BM25 search strips punctuation and treats quoted empty strings as noise.",
103092
- ` Example: searching for 'description: ""' with exact=false will NOT find empty description fields \u2014 it just matches "description".`,
103093
- ` Use exact=true for literal patterns like 'description: ""', 'value: \\'\\'', or any YAML/config field with specific punctuation.`,
103094
- "- Do NOT use exact=true for exploratory/conceptual queries \u2014 use the default for those.",
103095
- "",
103096
- "Combining searches with OR:",
103097
- '- Multiple unquoted words use OR logic: rate limit matches files containing EITHER "rate" OR "limit".',
103098
- `- IMPORTANT: Multiple quoted terms use AND logic by default: '"RateLimit" "middleware"' requires BOTH in the same file.`,
103099
- `- To search for ANY of several quoted symbols, use the explicit OR operator: '"ForwardMessage" OR "SessionLimiter"'.`,
103100
- '- Without quotes, camelCase like limitDRL gets split into "limit" + "DRL" \u2014 not what you want for symbol lookup.',
103101
- "- Use OR to search for multiple related symbols in ONE search instead of separate searches.",
103102
- "- This is much faster than running separate searches sequentially.",
103103
- `- Example: search '"ForwardMessage" OR "SessionLimiter"' finds files with either exact symbol in one call.`,
103104
- `- Example: search '"limitDRL" OR "doRollingWindowWrite"' finds both rate limiting functions at once.`,
103105
- "- Use AND (or just put quoted terms together) when you need both terms in the same file.",
103106
- "",
103107
- "Parallel tool calls:",
103108
- "- When you need to search for INDEPENDENT concepts, call multiple search tools IN PARALLEL (same response).",
103109
- "- Do NOT wait for one search to finish before starting the next if they are independent.",
103110
- '- Example: for "rate limiting and session management", call search "rate limiting" AND search "session management" in parallel.',
103111
- "- Similarly, call multiple extract tools in parallel when verifying different files.",
103112
- "",
103113
- "GOOD search strategy (do this):",
103114
- ' Query: "How does authentication work and how are sessions managed?"',
103115
- ' \u2192 search "authentication" + search "session management" IN PARALLEL (two independent concepts)',
103116
- ' Query: "Find the IP allowlist middleware"',
103117
- ' \u2192 search "allowlist middleware" (one search, probe handles IP/ip/Ip variations)',
103118
- ' Query: "Find ForwardMessage and SessionLimiter"',
103119
- ` \u2192 search '"ForwardMessage" OR "SessionLimiter"' (one OR search finds both exact symbols)`,
103120
- ' OR: search exact=true "ForwardMessage" + search exact=true "SessionLimiter" IN PARALLEL',
103121
- ' Query: "Find limitDRL and limitRedis functions"',
103122
- ` \u2192 search '"limitDRL" OR "limitRedis"' (one OR search, quoted to prevent camelCase splitting)`,
103123
- ' Query: "Find ThrottleRetryLimit usage"',
103124
- ' \u2192 search exact=true "ThrottleRetryLimit" (one search, if no results the symbol does not exist \u2014 stop)',
103125
- ' Query: "How does BM25 scoring work with SIMD optimization?"',
103126
- ' \u2192 search "BM25 scoring" + search "SIMD optimization" IN PARALLEL (two different concepts)',
103127
- "",
103128
- "BAD search strategy (never do this):",
103129
- ' \u2192 search "AllowedIPs" \u2192 search "allowedIps" \u2192 search "allowed_ips" (WRONG: case/style variations, probe handles them)',
103130
- ` \u2192 search "limitDRL" \u2192 search "LimitDRL" (WRONG: case variation \u2014 combine with OR: '"limitDRL" OR "limitRedis"')`,
103131
- ' \u2192 search "throttle_retry_limit" after searching "ThrottleRetryLimit" (WRONG: snake_case variation, probe handles it)',
103132
- ' \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)',
103133
- ' \u2192 search "func (k *RateLimitAndQuotaCheck) handleRateLimitFailure" (WRONG: do not search full function signatures, just use exact=true "handleRateLimitFailure")',
103134
- ' \u2192 search "ForwardMessage" \u2192 search "ForwardMessage" \u2192 search "ForwardMessage" (WRONG: repeating the exact same query)',
103135
- ' \u2192 search "authentication" \u2192 wait \u2192 search "session management" \u2192 wait (WRONG: these are independent, run them in parallel)',
103136
- "",
103137
- "Keyword tips:",
103138
- "- Common programming keywords are filtered as stopwords when unquoted: function, class, return, new, struct, impl, var, let, const, etc.",
103139
- '- Avoid searching for these alone \u2014 combine with a specific term (e.g., "middleware function" is fine, "function" alone is too generic).',
103140
- '- To bypass stopword filtering: wrap terms in quotes ("return", "struct") or set exact=true. Both disable stemming and splitting too.',
103141
- '- camelCase terms are split: getUserData becomes "get", "user", "data" \u2014 so one search covers all naming styles.',
103142
- '- Do NOT search for full function signatures like "func (r *Type) Method(args)". Just search for the method name with exact=true.',
103143
- '- Do NOT search for file names (e.g., "sliding_log.go"). Use listFiles to discover files by name.',
103144
- "",
103145
- "PAGINATION:",
103146
- "- Search results are paginated (~20k tokens per page).",
103147
- "- If your search returned relevant files, call the same query with nextPage=true to check for more.",
103148
- '- Keep paginating while results stay relevant. Stop when results are off-topic or "All results retrieved".',
103149
- "",
103150
- "WHEN TO STOP:",
103151
- "- After you have explored the main concept AND related subsystems.",
103152
- "- Once you have 5-15 targets covering different aspects of the query.",
103153
- '- If you get a "DUPLICATE SEARCH BLOCKED" message, do NOT rephrase the same query \u2014 try a FUNDAMENTALLY different approach:',
103154
- " * Switch between exact=true and exact=false",
103155
- " * Search for a broader term and filter results manually",
103156
- " * Use listFiles to browse the directory structure directly",
103157
- " * Look for related/surrounding patterns instead of the exact string",
103158
- "- If 2-3 genuinely different search approaches fail, STOP and report what you tried and why it failed.",
103159
- " Do NOT keep trying variations of the same failing concept.",
103160
- "",
103161
- "Strategy:",
103162
- "1. Analyze the query \u2014 identify key concepts, then brainstorm SYNONYMS and alternative terms for each.",
103163
- ' Code naming often differs from the concept: "authentication" \u2192 verify, credentials, login, auth;',
103164
- ' "rate limiting" \u2192 throttle, quota, limiter, bucket; "error handling" \u2192 catch, recover, panic.',
103165
- " Think about what a developer would NAME the function/struct/variable, not just the concept.",
103166
- "2. Run INDEPENDENT searches in PARALLEL \u2014 search for the main concept AND synonyms simultaneously.",
103167
- " After each search, check if results are relevant. If yes, call nextPage=true for more results.",
103168
- `3. Combine related symbols into OR searches: '"symbolA" OR "symbolB"' finds files with either.`,
103169
- "4. For known symbol names use exact=true. For concepts use default (exact=false).",
103170
- "5. After your first round of searches, READ the extracted code and look for connected code:",
103171
- " - Function calls to other important functions \u2192 include those targets.",
103172
- " - Type references and imports \u2192 include type definitions.",
103173
- " - Registered handlers/middleware \u2192 include all registered items.",
103174
- "6. If a search returns results, use extract to verify relevance. Run multiple extracts in parallel too.",
103175
- "7. If a search returns NO results, the term does not exist. Do NOT retry with variations. Move on.",
103176
- "8. Once you have enough targets (typically 5-15), output your final JSON answer immediately.",
103177
- "",
103178
- `Query: ${searchQuery}`,
103179
- `Search path(s): ${searchPath}`,
103180
- `Options: exact=${exact ? "true" : "false"}, language=${language || "auto"}, allow_tests=${allowTests ? "true" : "false"}.`,
103181
- "",
103182
- 'Return ONLY valid JSON: {"targets": ["path/to/file.ext#Symbol", "path/to/file.ext:line", "path/to/file.ext:start-end"]}',
103183
- '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.',
103184
- "Prefer #Symbol when a function/class name is clear; otherwise use line numbers.",
103185
- "Deduplicate targets. Do NOT explain or answer - ONLY return the JSON targets.",
103186
- "",
103187
- "Remember: if your search returned relevant results, use nextPage=true to check for more before outputting."
103188
- ].join("\n");
103214
+ return `<role>
103215
+ You are a code-location subagent. Your job is to find WHERE relevant code lives for the given question.
103216
+ You are NOT answering the question \u2014 you are finding the code locations that would help answer it.
103217
+ </role>
103218
+
103219
+ <task>
103220
+ <question>${searchQuery}</question>
103221
+ <search-path>${searchPath}</search-path>
103222
+ <options language="${language || "auto"}" allow_tests="${allowTests ? "true" : "false"}" />
103223
+ </task>
103224
+
103225
+ <tools>
103226
+ <tool name="search">
103227
+ Find code matching keywords or patterns. Results are paginated \u2014 use nextPage=true when results are relevant to get more.
103228
+ </tool>
103229
+ <tool name="extract">
103230
+ Read code to verify a file is actually relevant before including it.
103231
+ </tool>
103232
+ <tool name="listFiles">
103233
+ Browse directory structure to discover where code might live.
103234
+ </tool>
103235
+ </tools>
103236
+
103237
+ <search-engine-behavior>
103238
+ - Probe handles stemming, case-insensitive matching, and camelCase/snake_case splitting automatically.
103239
+ - "allowed_ips" ALREADY matches "AllowedIPs", "allowedIps", etc. Do NOT try case/style variations.
103240
+ - NEVER repeat the same search query \u2014 you will get the same results.
103241
+ - If a search returns no results at workspace root, the term does not exist. Move on.
103242
+ - If a search returns no results in a subfolder, try the workspace root or a different directory.
103243
+ - Use exact=true for known symbol names. Use default for conceptual/exploratory queries.
103244
+ - Combine related symbols with OR: "SymbolA" OR "SymbolB" finds files with either.
103245
+ - Run INDEPENDENT searches in PARALLEL \u2014 do not wait between unrelated searches.
103246
+ </search-engine-behavior>
103247
+
103248
+ <strategy>
103249
+ 1. Analyze the question \u2014 identify key concepts and brainstorm what a developer would NAME the relevant code.
103250
+ 2. Start your first search with the FULL search-path provided above. Do NOT narrow to a subdirectory on first try \u2014 the code may live anywhere in the tree.
103251
+ 3. Search for the main concept and synonyms in parallel.
103252
+ 4. Use extract to verify relevance \u2014 skim the code to confirm it ACTUALLY relates to the question.
103253
+ 5. Follow the trail: if you find a function, look for its callers, type definitions, and registered handlers.
103254
+ 6. Group your findings by WHY they are relevant (not by how you found them).
103255
+ </strategy>
103256
+
103257
+ <relevance-filtering priority="critical">
103258
+ - Only include files you have VERIFIED are relevant by reading them with extract.
103259
+ - Do NOT include files just because they matched a keyword \u2014 confirm the match is meaningful.
103260
+ - A file that mentions "session" in a comment is NOT relevant to "How do sessions work?" \u2014 look for the actual implementation.
103261
+ - Fewer verified-relevant files are far more valuable than many unverified keyword matches.
103262
+ - If a file is tangentially related but not core to the question, leave it out.
103263
+ - If NO files are truly relevant, return EMPTY groups with confidence "low". An honest empty result is far better than a wrong result. Never fill groups with loosely related files just to have something.
103264
+ </relevance-filtering>
103265
+
103266
+ <stop-conditions>
103267
+ - Once you have found locations covering the main concept and related subsystems.
103268
+ - If 2-3 different search approaches fail, stop and report what you have.
103269
+ - Do NOT keep trying quote/syntax variations of the same failing keyword.
103270
+ </stop-conditions>
103271
+
103272
+ <on-iteration-limit>
103273
+ If you run out of tool iterations, you MUST still output your JSON response with whatever you found so far.
103274
+ Set confidence to "low" if your search was incomplete.
103275
+ Include ALL files you verified as relevant, even if coverage is partial.
103276
+ The "searches" field helps the caller understand what was attempted.
103277
+ </on-iteration-limit>
103278
+
103279
+ <output-rules>
103280
+ - Return ONLY valid JSON matching the schema. No markdown, no explanation.
103281
+ - ONLY include files you have verified are relevant. No noise.
103282
+ - Group files by RELEVANCE to the question, not by search query.
103283
+ - Use ABSOLUTE file paths. Prefer #Symbol for functions/classes; otherwise use line ranges.
103284
+ - Deduplicate files across groups.
103285
+ </output-rules>`;
103189
103286
  }
103190
103287
  var import_ai5, import_fs12, CODE_SEARCH_SCHEMA, searchTool, queryTool, extractTool, delegateTool, analyzeAllTool;
103191
103288
  var init_vercel = __esm({
@@ -103201,17 +103298,54 @@ var init_vercel = __esm({
103201
103298
  import_fs12 = require("fs");
103202
103299
  init_error_types();
103203
103300
  init_hashline();
103301
+ init_provider();
103204
103302
  init_simpleTelemetry();
103205
103303
  CODE_SEARCH_SCHEMA = {
103206
103304
  type: "object",
103207
103305
  properties: {
103208
- targets: {
103306
+ confidence: {
103307
+ type: "string",
103308
+ enum: ["high", "medium", "low"],
103309
+ description: "How confident you are that these locations answer the question."
103310
+ },
103311
+ reason: {
103312
+ type: "string",
103313
+ description: "Brief explanation of confidence level \u2014 what was found, partially found, or not found."
103314
+ },
103315
+ groups: {
103209
103316
  type: "array",
103210
- items: { type: "string" },
103211
- description: 'List of file targets like "path/to/file.ext#Symbol" or "path/to/file.ext:line" or "path/to/file.ext:start-end".'
103317
+ items: {
103318
+ type: "object",
103319
+ properties: {
103320
+ reason: {
103321
+ type: "string",
103322
+ description: "Why these files are relevant \u2014 what aspect of the question they address (not how the code works)."
103323
+ },
103324
+ files: {
103325
+ type: "array",
103326
+ items: { type: "string" },
103327
+ description: 'File targets like "path/to/file.ext#Symbol" or "path/to/file.ext:10-20".'
103328
+ }
103329
+ },
103330
+ required: ["reason", "files"]
103331
+ },
103332
+ description: "Groups of related files, each with a reason explaining why they matter."
103333
+ },
103334
+ searches: {
103335
+ type: "array",
103336
+ items: {
103337
+ type: "object",
103338
+ properties: {
103339
+ query: { type: "string", description: "The search query used." },
103340
+ path: { type: "string", description: "The path searched in." },
103341
+ had_results: { type: "boolean", description: "Whether the search returned any results." }
103342
+ },
103343
+ required: ["query", "path", "had_results"]
103344
+ },
103345
+ description: "All search queries executed during this session, with their paths and outcomes."
103212
103346
  }
103213
103347
  },
103214
- required: ["targets"],
103348
+ required: ["confidence", "reason", "groups", "searches"],
103215
103349
  additionalProperties: false
103216
103350
  };
103217
103351
  searchTool = (options = {}) => {
@@ -103232,11 +103366,20 @@ var init_vercel = __esm({
103232
103366
  const previousSearches = /* @__PURE__ */ new Map();
103233
103367
  const dupBlockCounts = /* @__PURE__ */ new Map();
103234
103368
  const paginationCounts = /* @__PURE__ */ new Map();
103369
+ let consecutiveNoResults = 0;
103370
+ const MAX_CONSECUTIVE_NO_RESULTS = 4;
103371
+ const failedConcepts = /* @__PURE__ */ new Map();
103235
103372
  const MAX_PAGES_PER_QUERY = 3;
103373
+ const previousDelegations = [];
103374
+ let cachedDedupModel = void 0;
103375
+ function normalizeQueryConcept(query2) {
103376
+ if (!query2) return "";
103377
+ return query2.replace(/^["']|["']$/g, "").replace(/^(definition\s+of|implementation\s+of|usage\s+of|find|where\s+is|how\s+does|locate|show\s+me|get|look\s+for)\s+/i, "").replace(/^["']|["']$/g, "").replace(/\./g, "").replace(/[_\-\s]+/g, "").toLowerCase().trim();
103378
+ }
103236
103379
  return (0, import_ai5.tool)({
103237
103380
  name: "search",
103238
103381
  description: searchDelegate ? searchDelegateDescription : searchDescription,
103239
- inputSchema: searchSchema,
103382
+ inputSchema: searchDelegate ? searchDelegateSchema : searchSchema,
103240
103383
  execute: async ({ query: searchQuery, path: path9, allow_tests, exact, maxTokens: paramMaxTokens, language, session, nextPage, workingDirectory }) => {
103241
103384
  if (!exact && searchQuery) {
103242
103385
  const originalQuery = searchQuery;
@@ -103281,7 +103424,8 @@ var init_vercel = __esm({
103281
103424
  return await search(searchOptions);
103282
103425
  };
103283
103426
  if (!searchDelegate) {
103284
- const searchKey = `${searchPath}::${searchQuery}::${exact || false}`;
103427
+ const searchKey = `${searchPath}::${searchQuery}::${exact || false}::${language || ""}`;
103428
+ let circuitBreakerWarning = "";
103285
103429
  if (!nextPage) {
103286
103430
  if (previousSearches.has(searchKey)) {
103287
103431
  const blockCount = (dupBlockCounts.get(searchKey) || 0) + 1;
@@ -103301,6 +103445,35 @@ var init_vercel = __esm({
103301
103445
  }
103302
103446
  previousSearches.set(searchKey, { hadResults: false });
103303
103447
  paginationCounts.set(searchKey, 0);
103448
+ const normalizedKey = `${searchPath}::${normalizeQueryConcept(searchQuery)}`;
103449
+ if (failedConcepts.has(normalizedKey) && failedConcepts.get(normalizedKey) >= 2) {
103450
+ const conceptCount = failedConcepts.get(normalizedKey) + 1;
103451
+ failedConcepts.set(normalizedKey, conceptCount);
103452
+ if (debug) {
103453
+ console.error(`[CONCEPT-DEDUP] Blocked variation of failed concept (${conceptCount}x): "${searchQuery}" normalized to "${normalizeQueryConcept(searchQuery)}"`);
103454
+ }
103455
+ const isSubfolder = path9 && path9 !== effectiveSearchCwd && path9 !== ".";
103456
+ const scopeHint = isSubfolder ? `
103457
+ - Try searching from the workspace root (omit the path parameter) \u2014 the term may exist in a different directory` : `
103458
+ - The term does not exist in this codebase at any path`;
103459
+ return `CONCEPT ALREADY FAILED (${conceptCount} variations tried). You already searched for "${normalizeQueryConcept(searchQuery)}" with different quoting/syntax in this path and got NO results each time. Changing quotes, adding "func" prefix, or switching to method syntax will NOT change the results.
103460
+
103461
+ Change your strategy:${scopeHint}
103462
+ - Use extract on a file you ALREADY found to read actual code and discover real function/type names
103463
+ - Use listFiles to browse directories and find what functions actually exist
103464
+ - Search for a BROADER concept (e.g., instead of "ctxGetData", try "context" or "middleware data access")
103465
+ - If you have enough information from prior searches, provide your final answer NOW`;
103466
+ }
103467
+ if (consecutiveNoResults >= MAX_CONSECUTIVE_NO_RESULTS) {
103468
+ if (debug) {
103469
+ console.error(`[CIRCUIT-BREAKER] ${consecutiveNoResults} consecutive no-result searches, warning: "${searchQuery}"`);
103470
+ }
103471
+ const isSubfolderCB = path9 && path9 !== effectiveSearchCwd && path9 !== ".";
103472
+ const cbScopeHint = isSubfolderCB ? ` You have been searching in "${path9}" \u2014 consider searching from the workspace root or a different directory.` : "";
103473
+ circuitBreakerWarning = `
103474
+
103475
+ \u26A0\uFE0F CIRCUIT BREAKER: Your last ${consecutiveNoResults} searches ALL returned no results.${cbScopeHint} You MUST change your approach: use extract on files you already found, use listFiles to browse directories, or provide your final answer. Guessing names will not help.`;
103476
+ }
103304
103477
  } else {
103305
103478
  const pageCount = (paginationCounts.get(searchKey) || 0) + 1;
103306
103479
  paginationCounts.set(searchKey, pageCount);
@@ -103314,10 +103487,24 @@ var init_vercel = __esm({
103314
103487
  try {
103315
103488
  const result = maybeAnnotate(await runRawSearch());
103316
103489
  if (typeof result === "string" && result.includes("No results found")) {
103490
+ consecutiveNoResults++;
103491
+ const normalizedKey = `${searchPath}::${normalizeQueryConcept(searchQuery)}`;
103492
+ failedConcepts.set(normalizedKey, (failedConcepts.get(normalizedKey) || 0) + 1);
103493
+ if (debug) {
103494
+ console.error(`[NO-RESULTS] consecutiveNoResults=${consecutiveNoResults}, concept "${normalizeQueryConcept(searchQuery)}" failed ${failedConcepts.get(normalizedKey)}x`);
103495
+ }
103317
103496
  if (/^[A-Z]+-\d+$/.test(searchQuery.trim()) || /^[A-Z]+-\d+$/.test(searchQuery.replace(/"/g, "").trim())) {
103318
- return result + "\n\n\u26A0\uFE0F 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).";
103497
+ return result + "\n\n\u26A0\uFE0F 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)." + circuitBreakerWarning;
103498
+ }
103499
+ if (consecutiveNoResults >= MAX_CONSECUTIVE_NO_RESULTS - 1 && !circuitBreakerWarning) {
103500
+ const isSubfolderWarn = path9 && path9 !== effectiveSearchCwd && path9 !== ".";
103501
+ const warnScopeHint = isSubfolderWarn ? ` You are searching in "${path9}" \u2014 consider searching from the workspace root or a different directory.` : "";
103502
+ return result + `
103503
+
103504
+ \u26A0\uFE0F WARNING: ${consecutiveNoResults} consecutive searches returned no results.${warnScopeHint} Before your next action: use extract on a file you already found to read actual code, or use listFiles to discover what functions really exist. One more failed search will trigger the circuit breaker.`;
103319
103505
  }
103320
103506
  } else if (typeof result === "string") {
103507
+ consecutiveNoResults = 0;
103321
103508
  const entry = previousSearches.get(searchKey);
103322
103509
  if (entry) entry.hadResults = true;
103323
103510
  }
@@ -103325,7 +103512,7 @@ var init_vercel = __esm({
103325
103512
  options.fileTracker.trackFilesFromOutput(result, effectiveSearchCwd).catch(() => {
103326
103513
  });
103327
103514
  }
103328
- return result;
103515
+ return typeof result === "string" ? result + circuitBreakerWarning : result;
103329
103516
  } catch (error40) {
103330
103517
  console.error("Error executing search command:", error40);
103331
103518
  const formatted = formatErrorForAI(error40);
@@ -103335,12 +103522,58 @@ var init_vercel = __esm({
103335
103522
  return formatted;
103336
103523
  }
103337
103524
  }
103525
+ const delegatePath = searchPath || "";
103526
+ let effectiveQuery = searchQuery;
103527
+ if (previousDelegations.length > 0) {
103528
+ if (cachedDedupModel === void 0) {
103529
+ const dedupProvider = options.searchDelegateProvider || process.env.PROBE_SEARCH_DELEGATE_PROVIDER || options.provider || process.env.FORCE_PROVIDER || null;
103530
+ const dedupModelName = options.searchDelegateModel || process.env.PROBE_SEARCH_DELEGATE_MODEL || options.model || process.env.MODEL_NAME || null;
103531
+ if (debug) {
103532
+ console.error(`[DEDUP-LLM] Creating model: provider=${dedupProvider}, model=${dedupModelName}`);
103533
+ }
103534
+ cachedDedupModel = await createLanguageModel(dedupProvider, dedupModelName);
103535
+ if (debug) {
103536
+ console.error(`[DEDUP-LLM] Model created: ${cachedDedupModel ? "success" : "null"}`);
103537
+ }
103538
+ }
103539
+ const dedupSpanAttrs = {
103540
+ "dedup.query": searchQuery,
103541
+ "dedup.previous_count": String(previousDelegations.length),
103542
+ "dedup.previous_queries": previousDelegations.map((d) => d.query).join(" | ")
103543
+ };
103544
+ const dedup = options.tracer?.withSpan ? await options.tracer.withSpan("search.delegate.dedup", async () => {
103545
+ return await checkDelegateDedup(searchQuery, previousDelegations, cachedDedupModel, debug);
103546
+ }, dedupSpanAttrs, (span, result) => {
103547
+ span.setAttributes({
103548
+ "dedup.action": result.action,
103549
+ "dedup.reason": result.reason || "",
103550
+ "dedup.rewritten": result.rewritten || ""
103551
+ });
103552
+ }) : await checkDelegateDedup(searchQuery, previousDelegations, cachedDedupModel, debug);
103553
+ if (debug) {
103554
+ console.error(`[DEDUP-LLM] Query: "${searchQuery}" \u2192 ${dedup.action}: ${dedup.reason}${dedup.rewritten ? ` \u2192 "${dedup.rewritten}"` : ""}`);
103555
+ }
103556
+ if (dedup.action === "block") {
103557
+ const prevQueries = previousDelegations.map((d) => `"${d.query}"`).join(", ");
103558
+ return `DELEGATE BLOCKED: "${searchQuery}" is semantically duplicate of previous delegation(s) [${prevQueries}]. ${dedup.reason}
103559
+
103560
+ Do NOT re-delegate the same concept. Use extract() on files already found, or synthesize your answer from existing results.`;
103561
+ }
103562
+ if (dedup.action === "rewrite" && dedup.rewritten) {
103563
+ effectiveQuery = dedup.rewritten;
103564
+ if (debug) {
103565
+ console.error(`[DEDUP-LLM] Rewritten query: "${searchQuery}" \u2192 "${effectiveQuery}"`);
103566
+ }
103567
+ }
103568
+ }
103569
+ const delegationRecord = { query: effectiveQuery, path: delegatePath, hadResults: false };
103570
+ previousDelegations.push(delegationRecord);
103338
103571
  try {
103339
103572
  if (debug) {
103340
- console.error(`Delegating search with query: "${searchQuery}", path: "${searchPath}"`);
103573
+ console.error(`Delegating search with query: "${effectiveQuery}", path: "${searchPath}"${effectiveQuery !== searchQuery ? ` (rewritten from: "${searchQuery}")` : ""}`);
103341
103574
  }
103342
103575
  const delegateTask = buildSearchDelegateTask({
103343
- searchQuery,
103576
+ searchQuery: effectiveQuery,
103344
103577
  searchPath,
103345
103578
  exact,
103346
103579
  language,
@@ -103367,18 +103600,33 @@ var init_vercel = __esm({
103367
103600
  });
103368
103601
  const delegateResult = options.tracer?.withSpan ? await options.tracer.withSpan("search.delegate", runDelegation, {
103369
103602
  "search.query": searchQuery,
103370
- "search.path": searchPath
103603
+ "search.path": searchPath,
103604
+ ...effectiveQuery !== searchQuery ? { "search.query.rewritten": effectiveQuery } : {}
103371
103605
  }, (span, result) => {
103372
- const text = typeof result === "string" ? result : "";
103606
+ const text = typeof result === "string" ? result : JSON.stringify(result) || "";
103607
+ if (debug) console.error(`[search-delegate] onResult: type=${typeof result}, length=${text.length}`);
103373
103608
  span.setAttributes({
103374
103609
  "search.delegate.output": truncateForSpan(text),
103375
- "search.delegate.output_length": text.length
103610
+ "search.delegate.output_length": String(text.length)
103376
103611
  });
103377
103612
  }) : await runDelegation();
103378
- const targets = parseDelegatedTargets(delegateResult);
103379
- if (!targets.length) {
103613
+ const structured = parseDelegatedResponse(delegateResult);
103614
+ if (delegationRecord && structured) {
103615
+ delegationRecord.hadResults = structured.groups.length > 0;
103616
+ delegationRecord.reason = structured.reason || "";
103617
+ delegationRecord.groups = structured.groups.map((g) => ({ reason: g.reason }));
103618
+ }
103619
+ if (!structured || structured.groups.length === 0) {
103620
+ if (structured && structured.confidence === "low" && structured.reason) {
103621
+ if (debug) {
103622
+ console.error(`Delegated search explicitly found nothing: ${structured.reason}`);
103623
+ }
103624
+ return `NOT FOUND: The search delegate thoroughly searched for "${searchQuery}" and concluded: ${structured.reason}
103625
+
103626
+ Do NOT search for analogies or loosely related concepts. If the feature does not exist in the codebase, say so in your final answer.`;
103627
+ }
103380
103628
  if (debug) {
103381
- console.error("Delegated search returned no targets; falling back to raw search");
103629
+ console.error("Delegated search returned no results; falling back to raw search");
103382
103630
  }
103383
103631
  const fallbackResult = maybeAnnotate(await runRawSearch());
103384
103632
  if (options.fileTracker && typeof fallbackResult === "string") {
@@ -103389,57 +103637,35 @@ var init_vercel = __esm({
103389
103637
  }
103390
103638
  const delegateBase = options.allowedFolders?.[0] || options.cwd || ".";
103391
103639
  const resolutionBase = searchPaths[0] || options.cwd || ".";
103392
- const resolvedTargets = targets.map((target) => resolveTargetPath(target, delegateBase));
103393
- const validatedTargets = [];
103394
- for (const target of resolvedTargets) {
103395
- const { filePart, suffix } = splitTargetSuffix(target);
103396
- if ((0, import_fs12.existsSync)(filePart)) {
103397
- validatedTargets.push(target);
103398
- continue;
103399
- }
103400
- let fixed = false;
103401
- const parts = filePart.split("/").filter(Boolean);
103402
- for (let i = 0; i < parts.length - 1; i++) {
103403
- if (parts[i] === parts[i + 1]) {
103404
- const candidate = "/" + [...parts.slice(0, i), ...parts.slice(i + 1)].join("/");
103405
- if ((0, import_fs12.existsSync)(candidate)) {
103406
- validatedTargets.push(candidate + suffix);
103407
- if (debug) console.error(`[search-delegate] Fixed doubled path segment: ${filePart} \u2192 ${candidate}`);
103408
- fixed = true;
103409
- break;
103640
+ const wsPrefix = resolutionBase.endsWith("/") ? resolutionBase : resolutionBase + "/";
103641
+ for (const group of structured.groups) {
103642
+ group.files = group.files.map((target) => resolveTargetPath(target, delegateBase)).map((target) => {
103643
+ const { filePart, suffix } = splitTargetSuffix(target);
103644
+ if ((0, import_fs12.existsSync)(filePart)) return target;
103645
+ const parts = filePart.split("/").filter(Boolean);
103646
+ for (let i = 0; i < parts.length - 1; i++) {
103647
+ if (parts[i] === parts[i + 1]) {
103648
+ const candidate = "/" + [...parts.slice(0, i), ...parts.slice(i + 1)].join("/");
103649
+ if ((0, import_fs12.existsSync)(candidate)) {
103650
+ if (debug) console.error(`[search-delegate] Fixed doubled path: ${filePart} \u2192 ${candidate}`);
103651
+ return candidate + suffix;
103652
+ }
103410
103653
  }
103411
103654
  }
103412
- }
103413
- if (fixed) continue;
103414
- for (const altBase of [resolutionBase, options.cwd].filter(Boolean)) {
103415
- if (altBase === delegateBase) continue;
103416
- const altResolved = resolveTargetPath(target, altBase);
103417
- const { filePart: altFile } = splitTargetSuffix(altResolved);
103418
- if ((0, import_fs12.existsSync)(altFile)) {
103419
- validatedTargets.push(altResolved);
103420
- if (debug) console.error(`[search-delegate] Resolved with alt base: ${filePart} \u2192 ${altFile}`);
103421
- fixed = true;
103422
- break;
103655
+ for (const altBase of [resolutionBase, options.cwd].filter(Boolean)) {
103656
+ if (altBase === delegateBase) continue;
103657
+ const altResolved = resolveTargetPath(target, altBase);
103658
+ const { filePart: altFile } = splitTargetSuffix(altResolved);
103659
+ if ((0, import_fs12.existsSync)(altFile)) {
103660
+ if (debug) console.error(`[search-delegate] Resolved with alt base: ${filePart} \u2192 ${altFile}`);
103661
+ return altResolved;
103662
+ }
103423
103663
  }
103424
- }
103425
- if (fixed) continue;
103426
- if (debug) console.error(`[search-delegate] Warning: target may not exist: ${filePart}`);
103427
- validatedTargets.push(target);
103428
- }
103429
- const extractOptions = {
103430
- files: validatedTargets,
103431
- cwd: resolutionBase,
103432
- allowTests: allow_tests ?? true
103433
- };
103434
- if (outline) {
103435
- extractOptions.format = "xml";
103436
- }
103437
- const extractResult = await extract(extractOptions);
103438
- if (resolutionBase && typeof extractResult === "string") {
103439
- const wsPrefix = resolutionBase.endsWith("/") ? resolutionBase : resolutionBase + "/";
103440
- return maybeAnnotate(extractResult.split(wsPrefix).join(""));
103664
+ if (debug) console.error(`[search-delegate] Warning: target may not exist: ${filePart}`);
103665
+ return target;
103666
+ }).map((target) => target.split(wsPrefix).join(""));
103441
103667
  }
103442
- return maybeAnnotate(extractResult);
103668
+ return JSON.stringify(structured, null, 2);
103443
103669
  } catch (error40) {
103444
103670
  console.error("Delegated search failed, falling back to raw search:", error40);
103445
103671
  try {