@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/bin/binaries/{probe-v0.6.0-rc302-aarch64-apple-darwin.tar.gz → probe-v0.6.0-rc304-aarch64-apple-darwin.tar.gz} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc302-aarch64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc304-aarch64-unknown-linux-musl.tar.gz} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc302-x86_64-apple-darwin.tar.gz → probe-v0.6.0-rc304-x86_64-apple-darwin.tar.gz} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc302-x86_64-pc-windows-msvc.zip → probe-v0.6.0-rc304-x86_64-pc-windows-msvc.zip} +0 -0
- package/bin/binaries/{probe-v0.6.0-rc302-x86_64-unknown-linux-musl.tar.gz → probe-v0.6.0-rc304-x86_64-unknown-linux-musl.tar.gz} +0 -0
- package/build/agent/FallbackManager.js +3 -57
- package/build/agent/ProbeAgent.js +48 -62
- package/build/delegate.js +15 -4
- package/build/tools/common.js +16 -1
- package/build/tools/vercel.js +448 -209
- package/build/utils/provider.js +106 -0
- package/cjs/agent/ProbeAgent.cjs +1078 -305
- package/cjs/index.cjs +529 -303
- package/package.json +1 -1
- package/src/agent/FallbackManager.js +3 -57
- package/src/agent/ProbeAgent.js +48 -62
- package/src/delegate.js +15 -4
- package/src/tools/common.js +16 -1
- package/src/tools/vercel.js +448 -209
- package/src/utils/provider.js +106 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -8,10 +8,7 @@
|
|
|
8
8
|
* - Custom fallback chains with full configuration
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
12
|
-
import { createOpenAI } from '@ai-sdk/openai';
|
|
13
|
-
import { createGoogleGenerativeAI } from '@ai-sdk/google';
|
|
14
|
-
import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock';
|
|
11
|
+
import { createProviderInstance, DEFAULT_MODELS as SHARED_DEFAULT_MODELS } from '../utils/provider.js';
|
|
15
12
|
|
|
16
13
|
/**
|
|
17
14
|
* Fallback strategies
|
|
@@ -40,12 +37,7 @@ export const FALLBACK_STRATEGIES = {
|
|
|
40
37
|
/**
|
|
41
38
|
* Default model mappings for each provider
|
|
42
39
|
*/
|
|
43
|
-
const DEFAULT_MODELS =
|
|
44
|
-
anthropic: 'claude-sonnet-4-6',
|
|
45
|
-
openai: 'gpt-5.2',
|
|
46
|
-
google: 'gemini-2.5-flash',
|
|
47
|
-
bedrock: 'anthropic.claude-sonnet-4-6'
|
|
48
|
-
};
|
|
40
|
+
const DEFAULT_MODELS = SHARED_DEFAULT_MODELS;
|
|
49
41
|
|
|
50
42
|
/**
|
|
51
43
|
* FallbackManager class for handling provider and model fallback
|
|
@@ -138,53 +130,7 @@ export class FallbackManager {
|
|
|
138
130
|
*/
|
|
139
131
|
_createProviderInstance(config) {
|
|
140
132
|
try {
|
|
141
|
-
|
|
142
|
-
case 'anthropic':
|
|
143
|
-
return createAnthropic({
|
|
144
|
-
apiKey: config.apiKey,
|
|
145
|
-
...(config.baseURL && { baseURL: config.baseURL })
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
case 'openai':
|
|
149
|
-
return createOpenAI({
|
|
150
|
-
compatibility: 'strict',
|
|
151
|
-
apiKey: config.apiKey,
|
|
152
|
-
...(config.baseURL && { baseURL: config.baseURL })
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
case 'google':
|
|
156
|
-
return createGoogleGenerativeAI({
|
|
157
|
-
apiKey: config.apiKey,
|
|
158
|
-
...(config.baseURL && { baseURL: config.baseURL })
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
case 'bedrock': {
|
|
162
|
-
const bedrockConfig = {};
|
|
163
|
-
|
|
164
|
-
if (config.apiKey) {
|
|
165
|
-
bedrockConfig.apiKey = config.apiKey;
|
|
166
|
-
} else if (config.accessKeyId && config.secretAccessKey) {
|
|
167
|
-
bedrockConfig.accessKeyId = config.accessKeyId;
|
|
168
|
-
bedrockConfig.secretAccessKey = config.secretAccessKey;
|
|
169
|
-
if (config.sessionToken) {
|
|
170
|
-
bedrockConfig.sessionToken = config.sessionToken;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (config.region) {
|
|
175
|
-
bedrockConfig.region = config.region;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (config.baseURL) {
|
|
179
|
-
bedrockConfig.baseURL = config.baseURL;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return createAmazonBedrock(bedrockConfig);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
default:
|
|
186
|
-
throw new Error(`FallbackManager: Unknown provider "${config.provider}"`);
|
|
187
|
-
}
|
|
133
|
+
return createProviderInstance(config);
|
|
188
134
|
} catch (error) {
|
|
189
135
|
// Re-throw with more context
|
|
190
136
|
const providerName = this._getProviderDisplayName(config);
|
|
@@ -27,10 +27,7 @@ export const ENGINE_ACTIVITY_TIMEOUT_MIN = 5000;
|
|
|
27
27
|
*/
|
|
28
28
|
export const ENGINE_ACTIVITY_TIMEOUT_MAX = 600000;
|
|
29
29
|
|
|
30
|
-
import {
|
|
31
|
-
import { createOpenAI } from '@ai-sdk/openai';
|
|
32
|
-
import { createGoogleGenerativeAI } from '@ai-sdk/google';
|
|
33
|
-
import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock';
|
|
30
|
+
import { createProviderInstance, DEFAULT_MODELS } from '../utils/provider.js';
|
|
34
31
|
import { streamText, generateText, tool, stepCountIs, jsonSchema, Output } from 'ai';
|
|
35
32
|
import { randomUUID } from 'crypto';
|
|
36
33
|
import { EventEmitter } from 'events';
|
|
@@ -1673,13 +1670,10 @@ export class ProbeAgent {
|
|
|
1673
1670
|
* Initialize Anthropic model
|
|
1674
1671
|
*/
|
|
1675
1672
|
initializeAnthropicModel(apiKey, apiUrl, modelName) {
|
|
1676
|
-
this.provider =
|
|
1677
|
-
|
|
1678
|
-
...(apiUrl && { baseURL: apiUrl }),
|
|
1679
|
-
});
|
|
1680
|
-
this.model = modelName || 'claude-sonnet-4-6';
|
|
1673
|
+
this.provider = createProviderInstance({ provider: 'anthropic', apiKey, ...(apiUrl && { baseURL: apiUrl }) });
|
|
1674
|
+
this.model = modelName || DEFAULT_MODELS.anthropic;
|
|
1681
1675
|
this.apiType = 'anthropic';
|
|
1682
|
-
|
|
1676
|
+
|
|
1683
1677
|
if (this.debug) {
|
|
1684
1678
|
console.log(`Using Anthropic API with model: ${this.model}${apiUrl ? ` (URL: ${apiUrl})` : ''}`);
|
|
1685
1679
|
}
|
|
@@ -1689,14 +1683,10 @@ export class ProbeAgent {
|
|
|
1689
1683
|
* Initialize OpenAI model
|
|
1690
1684
|
*/
|
|
1691
1685
|
initializeOpenAIModel(apiKey, apiUrl, modelName) {
|
|
1692
|
-
this.provider =
|
|
1693
|
-
|
|
1694
|
-
apiKey: apiKey,
|
|
1695
|
-
...(apiUrl && { baseURL: apiUrl }),
|
|
1696
|
-
});
|
|
1697
|
-
this.model = modelName || 'gpt-5.2';
|
|
1686
|
+
this.provider = createProviderInstance({ provider: 'openai', apiKey, ...(apiUrl && { baseURL: apiUrl }) });
|
|
1687
|
+
this.model = modelName || DEFAULT_MODELS.openai;
|
|
1698
1688
|
this.apiType = 'openai';
|
|
1699
|
-
|
|
1689
|
+
|
|
1700
1690
|
if (this.debug) {
|
|
1701
1691
|
console.log(`Using OpenAI API with model: ${this.model}${apiUrl ? ` (URL: ${apiUrl})` : ''}`);
|
|
1702
1692
|
}
|
|
@@ -1706,10 +1696,7 @@ export class ProbeAgent {
|
|
|
1706
1696
|
* Initialize Google model
|
|
1707
1697
|
*/
|
|
1708
1698
|
initializeGoogleModel(apiKey, apiUrl, modelName) {
|
|
1709
|
-
this.provider =
|
|
1710
|
-
apiKey: apiKey,
|
|
1711
|
-
...(apiUrl && { baseURL: apiUrl }),
|
|
1712
|
-
});
|
|
1699
|
+
this.provider = createProviderInstance({ provider: 'google', apiKey, ...(apiUrl && { baseURL: apiUrl }) });
|
|
1713
1700
|
this.model = modelName || 'gemini-2.5-pro';
|
|
1714
1701
|
this.apiType = 'google';
|
|
1715
1702
|
|
|
@@ -2245,32 +2232,10 @@ export class ProbeAgent {
|
|
|
2245
2232
|
* Initialize AWS Bedrock model
|
|
2246
2233
|
*/
|
|
2247
2234
|
initializeBedrockModel(accessKeyId, secretAccessKey, region, sessionToken, apiKey, baseURL, modelName) {
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
if (apiKey) {
|
|
2253
|
-
config.apiKey = apiKey;
|
|
2254
|
-
} else if (accessKeyId && secretAccessKey) {
|
|
2255
|
-
config.accessKeyId = accessKeyId;
|
|
2256
|
-
config.secretAccessKey = secretAccessKey;
|
|
2257
|
-
if (sessionToken) {
|
|
2258
|
-
config.sessionToken = sessionToken;
|
|
2259
|
-
}
|
|
2260
|
-
}
|
|
2261
|
-
|
|
2262
|
-
// Region is required for AWS credentials but optional for API key
|
|
2263
|
-
if (region) {
|
|
2264
|
-
config.region = region;
|
|
2265
|
-
}
|
|
2266
|
-
|
|
2267
|
-
// Optional base URL
|
|
2268
|
-
if (baseURL) {
|
|
2269
|
-
config.baseURL = baseURL;
|
|
2270
|
-
}
|
|
2271
|
-
|
|
2272
|
-
this.provider = createAmazonBedrock(config);
|
|
2273
|
-
this.model = modelName || 'anthropic.claude-sonnet-4-6';
|
|
2235
|
+
this.provider = createProviderInstance({
|
|
2236
|
+
provider: 'bedrock', apiKey, accessKeyId, secretAccessKey, sessionToken, region, baseURL
|
|
2237
|
+
});
|
|
2238
|
+
this.model = modelName || DEFAULT_MODELS.bedrock;
|
|
2274
2239
|
this.apiType = 'bedrock';
|
|
2275
2240
|
|
|
2276
2241
|
if (this.debug) {
|
|
@@ -3012,7 +2977,7 @@ export class ProbeAgent {
|
|
|
3012
2977
|
|
|
3013
2978
|
// Add high-level instructions about when to use tools
|
|
3014
2979
|
const searchToolDesc1 = this.searchDelegate
|
|
3015
|
-
? '- search: Ask natural language questions to find code (e.g., "How does authentication work?").
|
|
2980
|
+
? '- 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 — just ask questions.'
|
|
3016
2981
|
: '- search: Find code patterns using keyword queries with Elasticsearch syntax. Handles stemming and case variations automatically — do NOT try manual keyword variations.';
|
|
3017
2982
|
systemPrompt += `You have access to powerful code search and analysis tools through MCP:
|
|
3018
2983
|
${searchToolDesc1}
|
|
@@ -3025,10 +2990,10 @@ ${searchToolDesc1}
|
|
|
3025
2990
|
}
|
|
3026
2991
|
|
|
3027
2992
|
const searchGuidance1 = this.searchDelegate
|
|
3028
|
-
? '1. Start with search — ask a question about what you want to understand. It returns
|
|
2993
|
+
? '1. Start with search — ask a question about what you want to understand. It returns file locations grouped by relevance (JSON with confidence and groups).'
|
|
3029
2994
|
: '1. Start with search to find relevant code patterns. One search per concept is usually enough — probe handles stemming and case variations.';
|
|
3030
2995
|
const extractGuidance1 = this.searchDelegate
|
|
3031
|
-
? '2. Use extract
|
|
2996
|
+
? '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.'
|
|
3032
2997
|
: '2. Use extract to get detailed context when needed';
|
|
3033
2998
|
|
|
3034
2999
|
systemPrompt += `\n
|
|
@@ -3078,7 +3043,7 @@ ${extractGuidance1}
|
|
|
3078
3043
|
|
|
3079
3044
|
// Add high-level instructions about when to use tools
|
|
3080
3045
|
const searchToolDesc2 = this.searchDelegate
|
|
3081
|
-
? '- search: Ask natural language questions to find code (e.g., "How does authentication work?").
|
|
3046
|
+
? '- 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 — just ask questions.'
|
|
3082
3047
|
: '- search: Find code patterns using keyword queries with Elasticsearch syntax. Handles stemming and case variations automatically — do NOT try manual keyword variations.';
|
|
3083
3048
|
systemPrompt += `You have access to powerful code search and analysis tools through MCP:
|
|
3084
3049
|
${searchToolDesc2}
|
|
@@ -3091,10 +3056,10 @@ ${searchToolDesc2}
|
|
|
3091
3056
|
}
|
|
3092
3057
|
|
|
3093
3058
|
const searchGuidance2 = this.searchDelegate
|
|
3094
|
-
? '1. Start with search — ask a question about what you want to understand. It returns
|
|
3059
|
+
? '1. Start with search — ask a question about what you want to understand. It returns file locations grouped by relevance (JSON with confidence and groups).'
|
|
3095
3060
|
: '1. Start with search to find relevant code patterns. One search per concept is usually enough — probe handles stemming and case variations.';
|
|
3096
3061
|
const extractGuidance2 = this.searchDelegate
|
|
3097
|
-
? '2. Use extract
|
|
3062
|
+
? '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.'
|
|
3098
3063
|
: '2. Use extract to get detailed context when needed';
|
|
3099
3064
|
|
|
3100
3065
|
systemPrompt += `\n
|
|
@@ -3160,10 +3125,10 @@ ${extractGuidance2}
|
|
|
3160
3125
|
Follow these instructions carefully:
|
|
3161
3126
|
1. Analyze the user's request.
|
|
3162
3127
|
2. Use the available tools step-by-step to fulfill the request.
|
|
3163
|
-
3. You MUST use the search tool before answering ANY code-related question. NEVER answer from memory or general knowledge — your answers must be grounded in actual code found via search/extract.${this.searchDelegate ? ' Ask natural language questions — the search subagent handles keyword formulation and returns
|
|
3128
|
+
3. You MUST use the search tool before answering ANY code-related question. NEVER answer from memory or general knowledge — your answers must be grounded in actual code found via search/extract.${this.searchDelegate ? ' Ask natural language questions — 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 — do NOT try keyword variations manually. Read full files only if really necessary.'}
|
|
3164
3129
|
4. Ensure to get really deep and understand the full picture before answering. Follow call chains — 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).
|
|
3165
3130
|
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 — go straight to the answer.
|
|
3166
|
-
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.'}
|
|
3131
|
+
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 — 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 — 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.'}
|
|
3167
3132
|
7. NEVER use bash for code exploration (no grep, cat, find, head, tail, awk, sed) — always use search and extract tools instead. Bash is only for system operations like building, running tests, or git commands.${this.allowEdit ? `
|
|
3168
3133
|
7. When modifying files, choose the appropriate tool:
|
|
3169
3134
|
- Use 'edit' for all code modifications:
|
|
@@ -4088,9 +4053,16 @@ or
|
|
|
4088
4053
|
const searchSummary = searchesTried.length > 0
|
|
4089
4054
|
? `\nSearches attempted: ${searchesTried.join(', ')}`
|
|
4090
4055
|
: '';
|
|
4056
|
+
|
|
4057
|
+
// For code-searcher subagents: instruct to output structured JSON even on partial results
|
|
4058
|
+
const isCodeSearcher = this.promptType === 'code-searcher';
|
|
4059
|
+
const lastIterMessage = isCodeSearcher
|
|
4060
|
+
? `⚠️ LAST ITERATION — 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}`
|
|
4061
|
+
: `⚠️ LAST ITERATION — 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}`;
|
|
4062
|
+
|
|
4091
4063
|
return {
|
|
4092
4064
|
toolChoice: 'none',
|
|
4093
|
-
userMessage:
|
|
4065
|
+
userMessage: lastIterMessage
|
|
4094
4066
|
};
|
|
4095
4067
|
}
|
|
4096
4068
|
|
|
@@ -4766,13 +4738,16 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
4766
4738
|
if (!finalResult || finalResult === DEFAULT_MAX_ITER_MSG) {
|
|
4767
4739
|
try {
|
|
4768
4740
|
const searchQueries = [];
|
|
4741
|
+
const searchDetails = [];
|
|
4769
4742
|
const toolCounts = {};
|
|
4770
4743
|
for (const tc of _toolCallLog) {
|
|
4771
4744
|
toolCounts[tc.name] = (toolCounts[tc.name] || 0) + 1;
|
|
4772
4745
|
if (tc.name === 'search') {
|
|
4773
4746
|
const q = tc.args.query || '';
|
|
4747
|
+
const p = tc.args.path || '.';
|
|
4774
4748
|
const exact = tc.args.exact ? ' (exact)' : '';
|
|
4775
4749
|
searchQueries.push(`"${q}"${exact}`);
|
|
4750
|
+
searchDetails.push({ query: q, path: p, had_results: false });
|
|
4776
4751
|
}
|
|
4777
4752
|
}
|
|
4778
4753
|
const toolBreakdown = Object.entries(toolCounts)
|
|
@@ -4780,13 +4755,24 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
4780
4755
|
.join(', ');
|
|
4781
4756
|
const uniqueSearches = [...new Set(searchQueries)];
|
|
4782
4757
|
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
if (
|
|
4786
|
-
|
|
4758
|
+
// For code-searcher subagents: produce structured JSON so the parent
|
|
4759
|
+
// can still use partial results instead of getting a plain error string.
|
|
4760
|
+
if (this.promptType === 'code-searcher') {
|
|
4761
|
+
finalResult = JSON.stringify({
|
|
4762
|
+
confidence: 'low',
|
|
4763
|
+
reason: 'Search incomplete — iteration limit reached',
|
|
4764
|
+
groups: [],
|
|
4765
|
+
searches: searchDetails
|
|
4766
|
+
});
|
|
4767
|
+
} else {
|
|
4768
|
+
let summary = `I was unable to complete your request after ${currentIteration} tool iterations.\n\n`;
|
|
4769
|
+
summary += `Tool calls made: ${toolBreakdown || 'none'}\n`;
|
|
4770
|
+
if (uniqueSearches.length > 0) {
|
|
4771
|
+
summary += `Search queries tried: ${uniqueSearches.join(', ')}\n`;
|
|
4772
|
+
}
|
|
4773
|
+
summary += `\nThe 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.`;
|
|
4774
|
+
finalResult = summary;
|
|
4787
4775
|
}
|
|
4788
|
-
summary += `\nThe 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.`;
|
|
4789
|
-
finalResult = summary;
|
|
4790
4776
|
} catch {
|
|
4791
4777
|
finalResult = DEFAULT_MAX_ITER_MSG;
|
|
4792
4778
|
}
|
package/build/delegate.js
CHANGED
|
@@ -20,13 +20,14 @@ import { ProbeAgent } from './agent/ProbeAgent.js';
|
|
|
20
20
|
*/
|
|
21
21
|
class DelegationManager {
|
|
22
22
|
constructor(options = {}) {
|
|
23
|
+
const parseSafe = (val, fallback) => { const n = parseInt(val, 10); return Number.isNaN(n) ? fallback : n; };
|
|
23
24
|
this.maxConcurrent = options.maxConcurrent
|
|
24
|
-
??
|
|
25
|
+
?? parseSafe(process.env.MAX_CONCURRENT_DELEGATIONS, 3);
|
|
25
26
|
this.maxPerSession = options.maxPerSession
|
|
26
|
-
??
|
|
27
|
+
?? parseSafe(process.env.MAX_DELEGATIONS_PER_SESSION, 10);
|
|
27
28
|
// Default queue timeout: 60 seconds. Set DELEGATION_QUEUE_TIMEOUT=0 to disable.
|
|
28
29
|
this.defaultQueueTimeout = options.queueTimeout
|
|
29
|
-
??
|
|
30
|
+
?? parseSafe(process.env.DELEGATION_QUEUE_TIMEOUT, 60000);
|
|
30
31
|
|
|
31
32
|
// Track delegations per session with timestamp for potential TTL cleanup
|
|
32
33
|
// Map<string, { count: number, lastUpdated: number }>
|
|
@@ -545,6 +546,9 @@ export async function delegate({
|
|
|
545
546
|
// Phase 2: Hard cancel after deadline if subagent hasn't finished
|
|
546
547
|
let parentAbortHandler;
|
|
547
548
|
let parentAbortHardCancelId = null;
|
|
549
|
+
// Track whether the race has settled so late abort signals don't create
|
|
550
|
+
// unhandled promise rejections.
|
|
551
|
+
let raceSettled = false;
|
|
548
552
|
const parentAbortPromise = new Promise((_, reject) => {
|
|
549
553
|
if (parentAbortSignal) {
|
|
550
554
|
if (parentAbortSignal.aborted) {
|
|
@@ -553,6 +557,8 @@ export async function delegate({
|
|
|
553
557
|
return;
|
|
554
558
|
}
|
|
555
559
|
parentAbortHandler = () => {
|
|
560
|
+
// If the race already settled, the answer won — don't reject.
|
|
561
|
+
if (raceSettled) return;
|
|
556
562
|
// Phase 1: graceful wind-down — let subagent finish its current step
|
|
557
563
|
subagent.triggerGracefulWindDown();
|
|
558
564
|
if (debug) {
|
|
@@ -567,6 +573,7 @@ export async function delegate({
|
|
|
567
573
|
}
|
|
568
574
|
// Phase 2: hard cancel after 30s if subagent hasn't finished
|
|
569
575
|
parentAbortHardCancelId = setTimeout(() => {
|
|
576
|
+
if (raceSettled) return;
|
|
570
577
|
if (debug) {
|
|
571
578
|
console.error(`[DELEGATE] Graceful wind-down deadline expired — hard cancelling subagent ${sessionId}`);
|
|
572
579
|
}
|
|
@@ -594,6 +601,8 @@ export async function delegate({
|
|
|
594
601
|
try {
|
|
595
602
|
response = await Promise.race(racers);
|
|
596
603
|
} finally {
|
|
604
|
+
// Mark race as settled so late abort signals are ignored
|
|
605
|
+
raceSettled = true;
|
|
597
606
|
// Clean up parent abort listener and hard cancel timer to prevent memory leaks
|
|
598
607
|
if (parentAbortHandler && parentAbortSignal) {
|
|
599
608
|
parentAbortSignal.removeEventListener('abort', parentAbortHandler);
|
|
@@ -650,10 +659,12 @@ export async function delegate({
|
|
|
650
659
|
});
|
|
651
660
|
|
|
652
661
|
if (delegationSpan) {
|
|
662
|
+
const { truncateForSpan } = await import('./agent/simpleTelemetry.js');
|
|
653
663
|
delegationSpan.setAttributes({
|
|
654
664
|
'delegation.result.success': true,
|
|
655
665
|
'delegation.result.response_length': response.length,
|
|
656
|
-
'delegation.result.duration_ms': duration
|
|
666
|
+
'delegation.result.duration_ms': duration,
|
|
667
|
+
'delegation.result': truncateForSpan(response, 4096)
|
|
657
668
|
});
|
|
658
669
|
delegationSpan.setStatus({ code: 1 }); // OK
|
|
659
670
|
delegationSpan.end();
|
package/build/tools/common.js
CHANGED
|
@@ -7,6 +7,11 @@ import { z } from 'zod';
|
|
|
7
7
|
import { resolve, isAbsolute } from 'path';
|
|
8
8
|
|
|
9
9
|
// Common schemas for tool parameters (used for internal execution after XML parsing)
|
|
10
|
+
export const searchDelegateSchema = z.object({
|
|
11
|
+
query: z.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 — just describe what you are looking for in plain English. A subagent will handle keyword searches for you.'),
|
|
12
|
+
path: z.string().optional().default('.').describe('Path to search in.'),
|
|
13
|
+
});
|
|
14
|
+
|
|
10
15
|
export const searchSchema = z.object({
|
|
11
16
|
query: z.string().describe('Search query — 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.'),
|
|
12
17
|
path: z.string().optional().default('.').describe('Path to search in. For dependencies use "go:github.com/owner/repo", "js:package_name", or "rust:cargo_name" etc.'),
|
|
@@ -100,7 +105,17 @@ export const attemptCompletionSchema = {
|
|
|
100
105
|
// Tool descriptions (used by Vercel tool() definitions)
|
|
101
106
|
|
|
102
107
|
export const 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 — do NOT manually try keyword variations like "getAllUsers" then "get_all_users" then "GetAllUsers". One search covers all variations.';
|
|
103
|
-
export const searchDelegateDescription =
|
|
108
|
+
export const 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.
|
|
109
|
+
|
|
110
|
+
Returns JSON: { "confidence": "high|medium|low", "groups": [{ "reason": "why these files matter", "files": ["path#Symbol", ...] }] }
|
|
111
|
+
|
|
112
|
+
IMPORTANT — each call spawns a subagent (expensive, takes minutes). Be deliberate:
|
|
113
|
+
- Ask plain English questions about WHERE code is, NOT keyword queries. Good: "How are user sessions extracted from cookies?" Bad: "ctxGetSession OR GetSession"
|
|
114
|
+
- Each call should explore a DIFFERENT ANGLE of the problem. Don't rephrase — reframe:
|
|
115
|
+
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?"
|
|
116
|
+
Bad: 1) "How does session extraction work?" 2) "Where is the session extracted?" 3) "Find session extraction code" ← same question reworded
|
|
117
|
+
- If a search returned no useful results, ask about a DIFFERENT part of the system. Think: what upstream/downstream component touches this?
|
|
118
|
+
- After getting results, use extract() to read the files you need — search only locates, extract reads.`;
|
|
104
119
|
export const queryDescription = 'Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.';
|
|
105
120
|
export const 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.';
|
|
106
121
|
export const 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.';
|