@probelabs/probe 0.6.0-rc288 → 0.6.0-rc290
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/binaries/probe-v0.6.0-rc290-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc290-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc290-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc290-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc290-x86_64-unknown-linux-musl.tar.gz +0 -0
- package/build/agent/ProbeAgent.js +61 -10
- package/build/agent/index.js +401 -86261
- package/build/agent/shared/prompts.js +27 -6
- package/build/extract.js +4 -2
- package/build/mcp/index.js +122 -9
- package/build/mcp/index.ts +162 -17
- package/build/search.js +6 -5
- package/build/tools/vercel.js +51 -22
- package/cjs/agent/ProbeAgent.cjs +131 -38
- package/cjs/index.cjs +131 -38
- package/package.json +2 -1
- package/src/agent/ProbeAgent.js +61 -10
- package/src/agent/shared/prompts.js +27 -6
- package/src/extract.js +4 -2
- package/src/mcp/index.ts +162 -17
- package/src/search.js +6 -5
- package/src/tools/vercel.js +51 -22
- package/bin/binaries/probe-v0.6.0-rc288-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc288-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc288-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc288-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc288-x86_64-unknown-linux-musl.tar.gz +0 -0
package/cjs/index.cjs
CHANGED
|
@@ -1944,7 +1944,8 @@ var init_search = __esm({
|
|
|
1944
1944
|
session: "--session",
|
|
1945
1945
|
timeout: "--timeout",
|
|
1946
1946
|
language: "--language",
|
|
1947
|
-
format: "--format"
|
|
1947
|
+
format: "--format",
|
|
1948
|
+
lsp: "--lsp"
|
|
1948
1949
|
};
|
|
1949
1950
|
}
|
|
1950
1951
|
});
|
|
@@ -2180,7 +2181,8 @@ var init_extract = __esm({
|
|
|
2180
2181
|
allowTests: "--allow-tests",
|
|
2181
2182
|
contextLines: "--context",
|
|
2182
2183
|
format: "--format",
|
|
2183
|
-
inputFile: "--input-file"
|
|
2184
|
+
inputFile: "--input-file",
|
|
2185
|
+
lsp: "--lsp"
|
|
2184
2186
|
};
|
|
2185
2187
|
}
|
|
2186
2188
|
});
|
|
@@ -73070,26 +73072,46 @@ var init_prompts = __esm({
|
|
|
73070
73072
|
CRITICAL - You are READ-ONLY:
|
|
73071
73073
|
You must NEVER create, modify, delete, or write files. You are strictly an exploration and analysis tool. If asked to make changes, implement features, fix bugs, or modify a PR, refuse and explain that file modifications must be done by the engineer tool \u2014 your role is only to investigate code and answer questions. Do not attempt workarounds using bash commands (echo, cat, tee, sed, etc.) to write files.
|
|
73072
73074
|
|
|
73075
|
+
CRITICAL - ALWAYS search before answering:
|
|
73076
|
+
You must NEVER answer questions about the codebase from memory or general knowledge. ALWAYS use the search and extract tools first to find the actual code, then base your answer ONLY on what you found. Even if you think you know the answer, you MUST verify it against the actual code. Your answers must be grounded in code evidence, not assumptions.
|
|
73077
|
+
|
|
73073
73078
|
When exploring code:
|
|
73074
73079
|
- Provide clear, concise explanations based on user request
|
|
73075
73080
|
- Find and highlight the most relevant code snippets, if required
|
|
73076
|
-
- Trace function calls and data flow through the system
|
|
73081
|
+
- Trace function calls and data flow through the system \u2014 follow the FULL call chain, not just the entry point
|
|
73077
73082
|
- Try to understand the user's intent and provide relevant information
|
|
73078
73083
|
- Understand high level picture
|
|
73079
73084
|
- Balance detail with clarity in your explanations
|
|
73085
|
+
- Search using SYNONYMS and alternative terms \u2014 code naming often differs from the concept name (e.g., "authentication" might be named verify_credentials, check_token, validate_session)
|
|
73086
|
+
- When you find a key function, look at what it CALLS and what CALLS it to discover the complete picture
|
|
73087
|
+
- Before answering, ask yourself: "Did I cover all the major components? Are there related subsystems I missed?" If yes, do one more search round.
|
|
73080
73088
|
|
|
73081
73089
|
When providing answers:
|
|
73090
|
+
- Be EXHAUSTIVE: cover ALL components you discovered, not just the main ones. If you found 10 related files, discuss all 10, not just the top 3. Users want the complete picture.
|
|
73091
|
+
- After drafting your answer, do a self-check: "What did I find in my searches that I haven't mentioned yet?" Add any missing components.
|
|
73092
|
+
- Include data structures, configuration options, and error handling \u2014 not just the happy path.
|
|
73082
73093
|
- Always include a "References" section at the end of your response
|
|
73083
73094
|
- List all relevant source code locations you found during exploration
|
|
73084
73095
|
- Use the format: file_path:line_number or file_path#symbol_name
|
|
73085
73096
|
- Group references by file when multiple locations are from the same file
|
|
73086
73097
|
- Include brief descriptions of what each reference contains`,
|
|
73087
|
-
"code-searcher": `You are ProbeChat Code
|
|
73098
|
+
"code-searcher": `You are ProbeChat Code Explorer & Searcher. Your job is to EXPLORE the codebase to find ALL relevant code locations for the query, then return them as JSON targets.
|
|
73099
|
+
|
|
73100
|
+
You think like a code explorer \u2014 you understand that codebases have layers:
|
|
73101
|
+
- Core implementations (algorithms, data structures)
|
|
73102
|
+
- Middleware/integration layers (request handlers, interceptors)
|
|
73103
|
+
- Configuration and storage backends
|
|
73104
|
+
- Scoping mechanisms (per-user, per-org, per-tenant, global)
|
|
73105
|
+
- Supporting utilities and helpers
|
|
73088
73106
|
|
|
73089
73107
|
When searching:
|
|
73090
|
-
-
|
|
73091
|
-
-
|
|
73092
|
-
-
|
|
73108
|
+
- Search for the MAIN concept first, then think: "what RELATED subsystems would a real codebase have?"
|
|
73109
|
+
- Use extract to READ the code you find \u2014 look for function calls, type references, and imports that point to OTHER relevant code
|
|
73110
|
+
- If you find middleware, check: are there org-level or tenant-level variants?
|
|
73111
|
+
- If you find algorithms, check: are there different storage backends?
|
|
73112
|
+
- Search results are paginated \u2014 if results look relevant, call nextPage=true to check for more files
|
|
73113
|
+
- Stop paginating when results become irrelevant or you see "All results retrieved"
|
|
73114
|
+
- Search using SYNONYMS \u2014 code naming differs from concepts (e.g., "rate limiting" \u2192 throttle, quota, limiter, bucket)
|
|
73093
73115
|
|
|
73094
73116
|
Output format (MANDATORY):
|
|
73095
73117
|
- Return ONLY valid JSON with a single top-level key: "targets"
|
|
@@ -73099,7 +73121,8 @@ Output format (MANDATORY):
|
|
|
73099
73121
|
- "path/to/file.ext:line"
|
|
73100
73122
|
- "path/to/file.ext:start-end"
|
|
73101
73123
|
- Prefer #SymbolName when a function/class name is clear; otherwise use line numbers
|
|
73102
|
-
- Deduplicate targets and keep them concise
|
|
73124
|
+
- Deduplicate targets and keep them concise
|
|
73125
|
+
- Aim for 5-15 targets covering ALL aspects of the query`,
|
|
73103
73126
|
"architect": `You are ProbeChat Architect, a specialized AI assistant focused on software architecture and design. Your primary function is to help users understand, analyze, and design software systems using the provided code analysis tools.
|
|
73104
73127
|
|
|
73105
73128
|
When analyzing code:
|
|
@@ -98753,9 +98776,9 @@ Workspace: ${this.allowedFolders.join(", ")}`;
|
|
|
98753
98776
|
Follow these instructions carefully:
|
|
98754
98777
|
1. Analyze the user's request.
|
|
98755
98778
|
2. Use the available tools step-by-step to fulfill the request.
|
|
98756
|
-
3. You
|
|
98757
|
-
4. Ensure to get really deep and understand the full picture before answering.
|
|
98758
|
-
5. Once the task is fully completed, provide your final answer directly as text.
|
|
98779
|
+
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."}
|
|
98780
|
+
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).
|
|
98781
|
+
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.
|
|
98759
98782
|
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."}
|
|
98760
98783
|
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 ? `
|
|
98761
98784
|
7. When modifying files, choose the appropriate tool:
|
|
@@ -99141,6 +99164,22 @@ You are working with a workspace. Available paths: ${workspaceDesc}
|
|
|
99141
99164
|
if (recentTexts.every((t) => t && t === recentTexts[0])) return true;
|
|
99142
99165
|
if (recentTexts.every((t) => detectStuckResponse(t))) return true;
|
|
99143
99166
|
}
|
|
99167
|
+
if (steps.length >= 3) {
|
|
99168
|
+
const last3 = steps.slice(-3);
|
|
99169
|
+
const allHaveTools = last3.every((s) => s.toolCalls?.length === 1);
|
|
99170
|
+
if (allHaveTools) {
|
|
99171
|
+
const signatures = last3.map((s) => {
|
|
99172
|
+
const tc = s.toolCalls[0];
|
|
99173
|
+
return `${tc.toolName}::${JSON.stringify(tc.args ?? tc.input)}`;
|
|
99174
|
+
});
|
|
99175
|
+
if (signatures[0] === signatures[1] && signatures[1] === signatures[2]) {
|
|
99176
|
+
if (this.debug) {
|
|
99177
|
+
console.log(`[DEBUG] Circuit breaker: 3 consecutive identical tool calls detected (${last3[0].toolCalls[0].toolName}), forcing stop`);
|
|
99178
|
+
}
|
|
99179
|
+
return true;
|
|
99180
|
+
}
|
|
99181
|
+
}
|
|
99182
|
+
}
|
|
99144
99183
|
return false;
|
|
99145
99184
|
},
|
|
99146
99185
|
prepareStep: ({ steps, stepNumber }) => {
|
|
@@ -99149,6 +99188,22 @@ You are working with a workspace. Available paths: ${workspaceDesc}
|
|
|
99149
99188
|
toolChoice: "none"
|
|
99150
99189
|
};
|
|
99151
99190
|
}
|
|
99191
|
+
if (steps.length >= 2) {
|
|
99192
|
+
const last2 = steps.slice(-2);
|
|
99193
|
+
if (last2.every((s) => s.toolCalls?.length === 1)) {
|
|
99194
|
+
const tc1 = last2[0].toolCalls[0];
|
|
99195
|
+
const tc2 = last2[1].toolCalls[0];
|
|
99196
|
+
const sig1 = `${tc1.toolName}::${JSON.stringify(tc1.args ?? tc1.input)}`;
|
|
99197
|
+
const sig2 = `${tc2.toolName}::${JSON.stringify(tc2.args ?? tc2.input)}`;
|
|
99198
|
+
if (sig1 === sig2) {
|
|
99199
|
+
if (this.debug) {
|
|
99200
|
+
console.log(`[DEBUG] prepareStep: 2 consecutive identical tool calls (${tc1.toolName}), forcing toolChoice=none`);
|
|
99201
|
+
console.log(`[DEBUG] sig: ${sig1.substring(0, 200)}`);
|
|
99202
|
+
}
|
|
99203
|
+
return { toolChoice: "none" };
|
|
99204
|
+
}
|
|
99205
|
+
}
|
|
99206
|
+
}
|
|
99152
99207
|
const lastStep = steps[steps.length - 1];
|
|
99153
99208
|
const modelJustStopped = lastStep?.finishReason === "stop" && (!lastStep?.toolCalls || lastStep.toolCalls.length === 0);
|
|
99154
99209
|
if (modelJustStopped) {
|
|
@@ -99179,7 +99234,9 @@ ${resultToReview}
|
|
|
99179
99234
|
|
|
99180
99235
|
Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If something needs to be fixed or is missing, do it now, then respond with the COMPLETE updated answer (everything you did in total, not just the fix).`;
|
|
99181
99236
|
return {
|
|
99182
|
-
userMessage: completionPromptMessage
|
|
99237
|
+
userMessage: completionPromptMessage,
|
|
99238
|
+
toolChoice: "none"
|
|
99239
|
+
// Force text-only review — no tool calls
|
|
99183
99240
|
};
|
|
99184
99241
|
}
|
|
99185
99242
|
}
|
|
@@ -99221,7 +99278,11 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
99221
99278
|
options.onStream(text);
|
|
99222
99279
|
}
|
|
99223
99280
|
if (this.debug) {
|
|
99224
|
-
|
|
99281
|
+
const toolSummary = toolCalls?.length ? toolCalls.map((tc) => {
|
|
99282
|
+
const args = tc.args ? JSON.stringify(tc.args) : "";
|
|
99283
|
+
return args ? `${tc.toolName}(${debugTruncate(args, 120)})` : tc.toolName;
|
|
99284
|
+
}).join(", ") : "none";
|
|
99285
|
+
console.log(`[DEBUG] Step ${currentIteration}/${maxIterations} finished (reason: ${finishReason}, tools: [${toolSummary}])`);
|
|
99225
99286
|
if (text) {
|
|
99226
99287
|
console.log(`[DEBUG] model text: ${debugTruncate(text)}`);
|
|
99227
99288
|
}
|
|
@@ -99254,9 +99315,15 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
99254
99315
|
}
|
|
99255
99316
|
const executeAIRequest = async () => {
|
|
99256
99317
|
const result = await this.streamTextWithRetryAndFallback(streamOptions);
|
|
99257
|
-
const
|
|
99318
|
+
const steps = await result.steps;
|
|
99319
|
+
let finalText;
|
|
99320
|
+
if (steps && steps.length > 1) {
|
|
99321
|
+
const lastStepText = steps[steps.length - 1].text;
|
|
99322
|
+
finalText = lastStepText || await result.text;
|
|
99323
|
+
} else {
|
|
99324
|
+
finalText = await result.text;
|
|
99325
|
+
}
|
|
99258
99326
|
if (this.debug) {
|
|
99259
|
-
const steps = await result.steps;
|
|
99260
99327
|
console.log(`[DEBUG] streamText completed: ${steps?.length || 0} steps, finalText=${finalText?.length || 0} chars`);
|
|
99261
99328
|
}
|
|
99262
99329
|
const usage = await result.usage;
|
|
@@ -99326,12 +99393,12 @@ ${finalResult}
|
|
|
99326
99393
|
|
|
99327
99394
|
Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If something needs to be fixed or is missing, do it now, then respond with the COMPLETE updated answer (everything you did in total, not just the fix).`;
|
|
99328
99395
|
currentMessages.push({ role: "user", content: completionPromptMessage });
|
|
99329
|
-
const completionMaxIterations = 5;
|
|
99330
99396
|
const completionStreamOptions = {
|
|
99331
99397
|
model: this.provider ? this.provider(this.model) : this.model,
|
|
99332
99398
|
messages: this.prepareMessagesWithImages(currentMessages),
|
|
99333
99399
|
tools: tools2,
|
|
99334
|
-
|
|
99400
|
+
toolChoice: "none",
|
|
99401
|
+
// Force text-only response — no tool calls during review
|
|
99335
99402
|
maxTokens: maxResponseTokens,
|
|
99336
99403
|
temperature: 0.3,
|
|
99337
99404
|
onStepFinish: ({ toolResults, text, finishReason, usage }) => {
|
|
@@ -101000,11 +101067,9 @@ function autoQuoteSearchTerms(query2) {
|
|
|
101000
101067
|
const result = tokens.map((token) => {
|
|
101001
101068
|
if (token.startsWith('"')) return token;
|
|
101002
101069
|
if (operators.has(token)) return token;
|
|
101003
|
-
const hasUpper = /[A-Z]/.test(token);
|
|
101004
|
-
const hasLower = /[a-z]/.test(token);
|
|
101005
101070
|
const hasUnderscore = token.includes("_");
|
|
101006
|
-
const
|
|
101007
|
-
if (
|
|
101071
|
+
const hasCaseTransition = /[a-z][A-Z]/.test(token) || /[A-Z]{2,}[a-z]/.test(token);
|
|
101072
|
+
if (hasCaseTransition || hasUnderscore) {
|
|
101008
101073
|
return `"${token}"`;
|
|
101009
101074
|
}
|
|
101010
101075
|
return token;
|
|
@@ -101119,7 +101184,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
101119
101184
|
"Break down complex queries into multiple searches to cover all aspects.",
|
|
101120
101185
|
"",
|
|
101121
101186
|
"Available tools:",
|
|
101122
|
-
"- search: Find code matching keywords or patterns. Run multiple searches for different aspects
|
|
101187
|
+
"- 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.",
|
|
101123
101188
|
"- extract: Verify code snippets to ensure targets are actually relevant before including them.",
|
|
101124
101189
|
"- listFiles: Understand directory structure to find where relevant code might live.",
|
|
101125
101190
|
"",
|
|
@@ -101140,13 +101205,14 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
101140
101205
|
"",
|
|
101141
101206
|
"Combining searches with OR:",
|
|
101142
101207
|
'- Multiple unquoted words use OR logic: rate limit matches files containing EITHER "rate" OR "limit".',
|
|
101143
|
-
`-
|
|
101208
|
+
`- IMPORTANT: Multiple quoted terms use AND logic by default: '"RateLimit" "middleware"' requires BOTH in the same file.`,
|
|
101209
|
+
`- To search for ANY of several quoted symbols, use the explicit OR operator: '"ForwardMessage" OR "SessionLimiter"'.`,
|
|
101144
101210
|
'- Without quotes, camelCase like limitDRL gets split into "limit" + "DRL" \u2014 not what you want for symbol lookup.',
|
|
101145
101211
|
"- Use OR to search for multiple related symbols in ONE search instead of separate searches.",
|
|
101146
101212
|
"- This is much faster than running separate searches sequentially.",
|
|
101147
|
-
`- Example: search '"ForwardMessage" "SessionLimiter"' finds files with either exact symbol in one call.`,
|
|
101148
|
-
`- Example: search '"limitDRL" "doRollingWindowWrite"' finds both rate limiting functions at once.`,
|
|
101149
|
-
|
|
101213
|
+
`- Example: search '"ForwardMessage" OR "SessionLimiter"' finds files with either exact symbol in one call.`,
|
|
101214
|
+
`- Example: search '"limitDRL" OR "doRollingWindowWrite"' finds both rate limiting functions at once.`,
|
|
101215
|
+
"- Use AND (or just put quoted terms together) when you need both terms in the same file.",
|
|
101150
101216
|
"",
|
|
101151
101217
|
"Parallel tool calls:",
|
|
101152
101218
|
"- When you need to search for INDEPENDENT concepts, call multiple search tools IN PARALLEL (same response).",
|
|
@@ -101160,10 +101226,10 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
101160
101226
|
' Query: "Find the IP allowlist middleware"',
|
|
101161
101227
|
' \u2192 search "allowlist middleware" (one search, probe handles IP/ip/Ip variations)',
|
|
101162
101228
|
' Query: "Find ForwardMessage and SessionLimiter"',
|
|
101163
|
-
` \u2192 search '"ForwardMessage" "SessionLimiter"' (one OR search finds both exact symbols)`,
|
|
101229
|
+
` \u2192 search '"ForwardMessage" OR "SessionLimiter"' (one OR search finds both exact symbols)`,
|
|
101164
101230
|
' OR: search exact=true "ForwardMessage" + search exact=true "SessionLimiter" IN PARALLEL',
|
|
101165
101231
|
' Query: "Find limitDRL and limitRedis functions"',
|
|
101166
|
-
` \u2192 search '"limitDRL" "limitRedis"' (one OR search, quoted to prevent camelCase splitting)`,
|
|
101232
|
+
` \u2192 search '"limitDRL" OR "limitRedis"' (one OR search, quoted to prevent camelCase splitting)`,
|
|
101167
101233
|
' Query: "Find ThrottleRetryLimit usage"',
|
|
101168
101234
|
' \u2192 search exact=true "ThrottleRetryLimit" (one search, if no results the symbol does not exist \u2014 stop)',
|
|
101169
101235
|
' Query: "How does BM25 scoring work with SIMD optimization?"',
|
|
@@ -101171,7 +101237,7 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
101171
101237
|
"",
|
|
101172
101238
|
"BAD search strategy (never do this):",
|
|
101173
101239
|
' \u2192 search "AllowedIPs" \u2192 search "allowedIps" \u2192 search "allowed_ips" (WRONG: case/style variations, probe handles them)',
|
|
101174
|
-
` \u2192 search "limitDRL" \u2192 search "LimitDRL" (WRONG: case variation \u2014 combine with OR: '"limitDRL" "limitRedis"')`,
|
|
101240
|
+
` \u2192 search "limitDRL" \u2192 search "LimitDRL" (WRONG: case variation \u2014 combine with OR: '"limitDRL" OR "limitRedis"')`,
|
|
101175
101241
|
' \u2192 search "throttle_retry_limit" after searching "ThrottleRetryLimit" (WRONG: snake_case variation, probe handles it)',
|
|
101176
101242
|
' \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)',
|
|
101177
101243
|
' \u2192 search "func (k *RateLimitAndQuotaCheck) handleRateLimitFailure" (WRONG: do not search full function signatures, just use exact=true "handleRateLimitFailure")',
|
|
@@ -101184,15 +101250,34 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
101184
101250
|
'- To bypass stopword filtering: wrap terms in quotes ("return", "struct") or set exact=true. Both disable stemming and splitting too.',
|
|
101185
101251
|
'- camelCase terms are split: getUserData becomes "get", "user", "data" \u2014 so one search covers all naming styles.',
|
|
101186
101252
|
'- Do NOT search for full function signatures like "func (r *Type) Method(args)". Just search for the method name with exact=true.',
|
|
101253
|
+
'- Do NOT search for file names (e.g., "sliding_log.go"). Use listFiles to discover files by name.',
|
|
101254
|
+
"",
|
|
101255
|
+
"PAGINATION:",
|
|
101256
|
+
"- Search results are paginated (~20k tokens per page).",
|
|
101257
|
+
"- If your search returned relevant files, call the same query with nextPage=true to check for more.",
|
|
101258
|
+
'- Keep paginating while results stay relevant. Stop when results are off-topic or "All results retrieved".',
|
|
101259
|
+
"",
|
|
101260
|
+
"WHEN TO STOP:",
|
|
101261
|
+
"- After you have explored the main concept AND related subsystems.",
|
|
101262
|
+
"- Once you have 5-15 targets covering different aspects of the query.",
|
|
101263
|
+
'- If you get a "DUPLICATE SEARCH BLOCKED" message, move on.',
|
|
101187
101264
|
"",
|
|
101188
101265
|
"Strategy:",
|
|
101189
|
-
"1. Analyze the query
|
|
101190
|
-
|
|
101191
|
-
"
|
|
101266
|
+
"1. Analyze the query \u2014 identify key concepts, then brainstorm SYNONYMS and alternative terms for each.",
|
|
101267
|
+
' Code naming often differs from the concept: "authentication" \u2192 verify, credentials, login, auth;',
|
|
101268
|
+
' "rate limiting" \u2192 throttle, quota, limiter, bucket; "error handling" \u2192 catch, recover, panic.',
|
|
101269
|
+
" Think about what a developer would NAME the function/struct/variable, not just the concept.",
|
|
101270
|
+
"2. Run INDEPENDENT searches in PARALLEL \u2014 search for the main concept AND synonyms simultaneously.",
|
|
101271
|
+
" After each search, check if results are relevant. If yes, call nextPage=true for more results.",
|
|
101272
|
+
`3. Combine related symbols into OR searches: '"symbolA" OR "symbolB"' finds files with either.`,
|
|
101192
101273
|
"4. For known symbol names use exact=true. For concepts use default (exact=false).",
|
|
101193
|
-
"5.
|
|
101194
|
-
"
|
|
101195
|
-
"
|
|
101274
|
+
"5. After your first round of searches, READ the extracted code and look for connected code:",
|
|
101275
|
+
" - Function calls to other important functions \u2192 include those targets.",
|
|
101276
|
+
" - Type references and imports \u2192 include type definitions.",
|
|
101277
|
+
" - Registered handlers/middleware \u2192 include all registered items.",
|
|
101278
|
+
"6. If a search returns results, use extract to verify relevance. Run multiple extracts in parallel too.",
|
|
101279
|
+
"7. If a search returns NO results, the term does not exist. Do NOT retry with variations. Move on.",
|
|
101280
|
+
"8. Once you have enough targets (typically 5-15), output your final JSON answer immediately.",
|
|
101196
101281
|
"",
|
|
101197
101282
|
`Query: ${searchQuery}`,
|
|
101198
101283
|
`Search path(s): ${searchPath}`,
|
|
@@ -101201,7 +101286,9 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
101201
101286
|
'Return ONLY valid JSON: {"targets": ["path/to/file.ext#Symbol", "path/to/file.ext:line", "path/to/file.ext:start-end"]}',
|
|
101202
101287
|
'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.',
|
|
101203
101288
|
"Prefer #Symbol when a function/class name is clear; otherwise use line numbers.",
|
|
101204
|
-
"Deduplicate targets. Do NOT explain or answer - ONLY return the JSON targets."
|
|
101289
|
+
"Deduplicate targets. Do NOT explain or answer - ONLY return the JSON targets.",
|
|
101290
|
+
"",
|
|
101291
|
+
"Remember: if your search returned relevant results, use nextPage=true to check for more before outputting."
|
|
101205
101292
|
].join("\n");
|
|
101206
101293
|
}
|
|
101207
101294
|
var import_ai5, import_fs11, CODE_SEARCH_SCHEMA, searchTool, queryTool, extractTool, delegateTool, analyzeAllTool;
|
|
@@ -101246,6 +101333,7 @@ var init_vercel = __esm({
|
|
|
101246
101333
|
return result;
|
|
101247
101334
|
};
|
|
101248
101335
|
const previousSearches = /* @__PURE__ */ new Set();
|
|
101336
|
+
let consecutiveDupBlocks = 0;
|
|
101249
101337
|
const paginationCounts = /* @__PURE__ */ new Map();
|
|
101250
101338
|
const MAX_PAGES_PER_QUERY = 3;
|
|
101251
101339
|
return (0, import_ai5.tool)({
|
|
@@ -101298,12 +101386,17 @@ var init_vercel = __esm({
|
|
|
101298
101386
|
const searchKey = `${searchQuery}::${exact || false}`;
|
|
101299
101387
|
if (!nextPage) {
|
|
101300
101388
|
if (previousSearches.has(searchKey)) {
|
|
101389
|
+
consecutiveDupBlocks++;
|
|
101301
101390
|
if (debug) {
|
|
101302
|
-
console.error(`[DEDUP] Blocked duplicate search: "${searchQuery}" (path: "${searchPath}")`);
|
|
101391
|
+
console.error(`[DEDUP] Blocked duplicate search (${consecutiveDupBlocks}x): "${searchQuery}" (path: "${searchPath}")`);
|
|
101392
|
+
}
|
|
101393
|
+
if (consecutiveDupBlocks >= 3) {
|
|
101394
|
+
return "STOP. You have been blocked " + consecutiveDupBlocks + " times for repeating searches. You MUST output your final JSON answer NOW with whatever targets you have found. Do NOT call any more tools.";
|
|
101303
101395
|
}
|
|
101304
|
-
return "DUPLICATE SEARCH BLOCKED
|
|
101396
|
+
return "DUPLICATE SEARCH BLOCKED (" + consecutiveDupBlocks + "x). You already searched for this. Do NOT repeat \u2014 probe searches recursively across all paths. Either: (1) use extract on results you already found, (2) try a COMPLETELY different keyword, or (3) output your final answer NOW.";
|
|
101305
101397
|
}
|
|
101306
101398
|
previousSearches.add(searchKey);
|
|
101399
|
+
consecutiveDupBlocks = 0;
|
|
101307
101400
|
paginationCounts.set(searchKey, 0);
|
|
101308
101401
|
} else {
|
|
101309
101402
|
const pageCount = (paginationCounts.get(searchKey) || 0) + 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@probelabs/probe",
|
|
3
|
-
"version": "0.6.0-
|
|
3
|
+
"version": "0.6.0-rc290",
|
|
4
4
|
"description": "Node.js wrapper for the probe code search tool",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
],
|
|
50
50
|
"scripts": {
|
|
51
51
|
"postinstall": "node scripts/postinstall.js",
|
|
52
|
+
"prepare": "npm run build:mcp",
|
|
52
53
|
"build:mcp": "node scripts/build-mcp.cjs",
|
|
53
54
|
"build:agent": "node scripts/build-agent.cjs",
|
|
54
55
|
"build:types": "echo 'TypeScript definitions already manually created'",
|
package/src/agent/ProbeAgent.js
CHANGED
|
@@ -2976,9 +2976,9 @@ ${extractGuidance2}
|
|
|
2976
2976
|
Follow these instructions carefully:
|
|
2977
2977
|
1. Analyze the user's request.
|
|
2978
2978
|
2. Use the available tools step-by-step to fulfill the request.
|
|
2979
|
-
3. You
|
|
2980
|
-
4. Ensure to get really deep and understand the full picture before answering.
|
|
2981
|
-
5. Once the task is fully completed, provide your final answer directly as text.
|
|
2979
|
+
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 extracted code blocks. Use extract only to expand context or read full files.' : ' Search handles stemming and case variations automatically — do NOT try keyword variations manually. Read full files only if really necessary.'}
|
|
2980
|
+
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).
|
|
2981
|
+
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.
|
|
2982
2982
|
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.'}
|
|
2983
2983
|
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 ? `
|
|
2984
2984
|
7. When modifying files, choose the appropriate tool:
|
|
@@ -3483,6 +3483,24 @@ Follow these instructions carefully:
|
|
|
3483
3483
|
if (recentTexts.every(t => detectStuckResponse(t))) return true;
|
|
3484
3484
|
}
|
|
3485
3485
|
|
|
3486
|
+
// Circuit breaker: repeated identical tool calls (e.g. model ignores dedup message)
|
|
3487
|
+
if (steps.length >= 3) {
|
|
3488
|
+
const last3 = steps.slice(-3);
|
|
3489
|
+
const allHaveTools = last3.every(s => s.toolCalls?.length === 1);
|
|
3490
|
+
if (allHaveTools) {
|
|
3491
|
+
const signatures = last3.map(s => {
|
|
3492
|
+
const tc = s.toolCalls[0];
|
|
3493
|
+
return `${tc.toolName}::${JSON.stringify(tc.args ?? tc.input)}`;
|
|
3494
|
+
});
|
|
3495
|
+
if (signatures[0] === signatures[1] && signatures[1] === signatures[2]) {
|
|
3496
|
+
if (this.debug) {
|
|
3497
|
+
console.log(`[DEBUG] Circuit breaker: 3 consecutive identical tool calls detected (${last3[0].toolCalls[0].toolName}), forcing stop`);
|
|
3498
|
+
}
|
|
3499
|
+
return true;
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3486
3504
|
return false;
|
|
3487
3505
|
},
|
|
3488
3506
|
prepareStep: ({ steps, stepNumber }) => {
|
|
@@ -3493,6 +3511,24 @@ Follow these instructions carefully:
|
|
|
3493
3511
|
};
|
|
3494
3512
|
}
|
|
3495
3513
|
|
|
3514
|
+
// Force text-only response after 2 consecutive identical tool calls
|
|
3515
|
+
if (steps.length >= 2) {
|
|
3516
|
+
const last2 = steps.slice(-2);
|
|
3517
|
+
if (last2.every(s => s.toolCalls?.length === 1)) {
|
|
3518
|
+
const tc1 = last2[0].toolCalls[0];
|
|
3519
|
+
const tc2 = last2[1].toolCalls[0];
|
|
3520
|
+
const sig1 = `${tc1.toolName}::${JSON.stringify(tc1.args ?? tc1.input)}`;
|
|
3521
|
+
const sig2 = `${tc2.toolName}::${JSON.stringify(tc2.args ?? tc2.input)}`;
|
|
3522
|
+
if (sig1 === sig2) {
|
|
3523
|
+
if (this.debug) {
|
|
3524
|
+
console.log(`[DEBUG] prepareStep: 2 consecutive identical tool calls (${tc1.toolName}), forcing toolChoice=none`);
|
|
3525
|
+
console.log(`[DEBUG] sig: ${sig1.substring(0, 200)}`);
|
|
3526
|
+
}
|
|
3527
|
+
return { toolChoice: 'none' };
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
|
|
3496
3532
|
const lastStep = steps[steps.length - 1];
|
|
3497
3533
|
const modelJustStopped = lastStep?.finishReason === 'stop'
|
|
3498
3534
|
&& (!lastStep?.toolCalls || lastStep.toolCalls.length === 0);
|
|
@@ -3532,7 +3568,8 @@ ${resultToReview}
|
|
|
3532
3568
|
Double-check your response based on the criteria above. If everything looks good, respond with your previous answer exactly as-is. If something needs to be fixed or is missing, do it now, then respond with the COMPLETE updated answer (everything you did in total, not just the fix).`;
|
|
3533
3569
|
|
|
3534
3570
|
return {
|
|
3535
|
-
userMessage: completionPromptMessage
|
|
3571
|
+
userMessage: completionPromptMessage,
|
|
3572
|
+
toolChoice: 'none' // Force text-only review — no tool calls
|
|
3536
3573
|
};
|
|
3537
3574
|
}
|
|
3538
3575
|
}
|
|
@@ -3585,7 +3622,13 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
3585
3622
|
}
|
|
3586
3623
|
|
|
3587
3624
|
if (this.debug) {
|
|
3588
|
-
|
|
3625
|
+
const toolSummary = toolCalls?.length
|
|
3626
|
+
? toolCalls.map(tc => {
|
|
3627
|
+
const args = tc.args ? JSON.stringify(tc.args) : '';
|
|
3628
|
+
return args ? `${tc.toolName}(${debugTruncate(args, 120)})` : tc.toolName;
|
|
3629
|
+
}).join(', ')
|
|
3630
|
+
: 'none';
|
|
3631
|
+
console.log(`[DEBUG] Step ${currentIteration}/${maxIterations} finished (reason: ${finishReason}, tools: [${toolSummary}])`);
|
|
3589
3632
|
if (text) {
|
|
3590
3633
|
console.log(`[DEBUG] model text: ${debugTruncate(text)}`);
|
|
3591
3634
|
}
|
|
@@ -3627,11 +3670,20 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
3627
3670
|
const executeAIRequest = async () => {
|
|
3628
3671
|
const result = await this.streamTextWithRetryAndFallback(streamOptions);
|
|
3629
3672
|
|
|
3630
|
-
//
|
|
3631
|
-
|
|
3673
|
+
// Use only the last step's text as the final answer.
|
|
3674
|
+
// result.text concatenates ALL steps (including intermediate planning text),
|
|
3675
|
+
// but the user should only see the final answer from the last step.
|
|
3676
|
+
const steps = await result.steps;
|
|
3677
|
+
let finalText;
|
|
3678
|
+
if (steps && steps.length > 1) {
|
|
3679
|
+
// Multi-step: use last step's text (the actual answer after tool calls)
|
|
3680
|
+
const lastStepText = steps[steps.length - 1].text;
|
|
3681
|
+
finalText = lastStepText || await result.text;
|
|
3682
|
+
} else {
|
|
3683
|
+
finalText = await result.text;
|
|
3684
|
+
}
|
|
3632
3685
|
|
|
3633
3686
|
if (this.debug) {
|
|
3634
|
-
const steps = await result.steps;
|
|
3635
3687
|
console.log(`[DEBUG] streamText completed: ${steps?.length || 0} steps, finalText=${finalText?.length || 0} chars`);
|
|
3636
3688
|
}
|
|
3637
3689
|
|
|
@@ -3726,12 +3778,11 @@ Double-check your response based on the criteria above. If everything looks good
|
|
|
3726
3778
|
|
|
3727
3779
|
currentMessages.push({ role: 'user', content: completionPromptMessage });
|
|
3728
3780
|
|
|
3729
|
-
const completionMaxIterations = 5;
|
|
3730
3781
|
const completionStreamOptions = {
|
|
3731
3782
|
model: this.provider ? this.provider(this.model) : this.model,
|
|
3732
3783
|
messages: this.prepareMessagesWithImages(currentMessages),
|
|
3733
3784
|
tools,
|
|
3734
|
-
|
|
3785
|
+
toolChoice: 'none', // Force text-only response — no tool calls during review
|
|
3735
3786
|
maxTokens: maxResponseTokens,
|
|
3736
3787
|
temperature: 0.3,
|
|
3737
3788
|
onStepFinish: ({ toolResults, text, finishReason, usage }) => {
|
|
@@ -8,27 +8,47 @@ export const predefinedPrompts = {
|
|
|
8
8
|
CRITICAL - You are READ-ONLY:
|
|
9
9
|
You must NEVER create, modify, delete, or write files. You are strictly an exploration and analysis tool. If asked to make changes, implement features, fix bugs, or modify a PR, refuse and explain that file modifications must be done by the engineer tool — your role is only to investigate code and answer questions. Do not attempt workarounds using bash commands (echo, cat, tee, sed, etc.) to write files.
|
|
10
10
|
|
|
11
|
+
CRITICAL - ALWAYS search before answering:
|
|
12
|
+
You must NEVER answer questions about the codebase from memory or general knowledge. ALWAYS use the search and extract tools first to find the actual code, then base your answer ONLY on what you found. Even if you think you know the answer, you MUST verify it against the actual code. Your answers must be grounded in code evidence, not assumptions.
|
|
13
|
+
|
|
11
14
|
When exploring code:
|
|
12
15
|
- Provide clear, concise explanations based on user request
|
|
13
16
|
- Find and highlight the most relevant code snippets, if required
|
|
14
|
-
- Trace function calls and data flow through the system
|
|
17
|
+
- Trace function calls and data flow through the system — follow the FULL call chain, not just the entry point
|
|
15
18
|
- Try to understand the user's intent and provide relevant information
|
|
16
19
|
- Understand high level picture
|
|
17
20
|
- Balance detail with clarity in your explanations
|
|
21
|
+
- Search using SYNONYMS and alternative terms — code naming often differs from the concept name (e.g., "authentication" might be named verify_credentials, check_token, validate_session)
|
|
22
|
+
- When you find a key function, look at what it CALLS and what CALLS it to discover the complete picture
|
|
23
|
+
- Before answering, ask yourself: "Did I cover all the major components? Are there related subsystems I missed?" If yes, do one more search round.
|
|
18
24
|
|
|
19
25
|
When providing answers:
|
|
26
|
+
- Be EXHAUSTIVE: cover ALL components you discovered, not just the main ones. If you found 10 related files, discuss all 10, not just the top 3. Users want the complete picture.
|
|
27
|
+
- After drafting your answer, do a self-check: "What did I find in my searches that I haven't mentioned yet?" Add any missing components.
|
|
28
|
+
- Include data structures, configuration options, and error handling — not just the happy path.
|
|
20
29
|
- Always include a "References" section at the end of your response
|
|
21
30
|
- List all relevant source code locations you found during exploration
|
|
22
31
|
- Use the format: file_path:line_number or file_path#symbol_name
|
|
23
32
|
- Group references by file when multiple locations are from the same file
|
|
24
33
|
- Include brief descriptions of what each reference contains`,
|
|
25
34
|
|
|
26
|
-
'code-searcher': `You are ProbeChat Code
|
|
35
|
+
'code-searcher': `You are ProbeChat Code Explorer & Searcher. Your job is to EXPLORE the codebase to find ALL relevant code locations for the query, then return them as JSON targets.
|
|
36
|
+
|
|
37
|
+
You think like a code explorer — you understand that codebases have layers:
|
|
38
|
+
- Core implementations (algorithms, data structures)
|
|
39
|
+
- Middleware/integration layers (request handlers, interceptors)
|
|
40
|
+
- Configuration and storage backends
|
|
41
|
+
- Scoping mechanisms (per-user, per-org, per-tenant, global)
|
|
42
|
+
- Supporting utilities and helpers
|
|
27
43
|
|
|
28
44
|
When searching:
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
45
|
+
- Search for the MAIN concept first, then think: "what RELATED subsystems would a real codebase have?"
|
|
46
|
+
- Use extract to READ the code you find — look for function calls, type references, and imports that point to OTHER relevant code
|
|
47
|
+
- If you find middleware, check: are there org-level or tenant-level variants?
|
|
48
|
+
- If you find algorithms, check: are there different storage backends?
|
|
49
|
+
- Search results are paginated — if results look relevant, call nextPage=true to check for more files
|
|
50
|
+
- Stop paginating when results become irrelevant or you see "All results retrieved"
|
|
51
|
+
- Search using SYNONYMS — code naming differs from concepts (e.g., "rate limiting" → throttle, quota, limiter, bucket)
|
|
32
52
|
|
|
33
53
|
Output format (MANDATORY):
|
|
34
54
|
- Return ONLY valid JSON with a single top-level key: "targets"
|
|
@@ -38,7 +58,8 @@ Output format (MANDATORY):
|
|
|
38
58
|
- "path/to/file.ext:line"
|
|
39
59
|
- "path/to/file.ext:start-end"
|
|
40
60
|
- Prefer #SymbolName when a function/class name is clear; otherwise use line numbers
|
|
41
|
-
- Deduplicate targets and keep them concise
|
|
61
|
+
- Deduplicate targets and keep them concise
|
|
62
|
+
- Aim for 5-15 targets covering ALL aspects of the query`,
|
|
42
63
|
|
|
43
64
|
'architect': `You are ProbeChat Architect, a specialized AI assistant focused on software architecture and design. Your primary function is to help users understand, analyze, and design software systems using the provided code analysis tools.
|
|
44
65
|
|
package/src/extract.js
CHANGED
|
@@ -18,7 +18,8 @@ const EXTRACT_FLAG_MAP = {
|
|
|
18
18
|
allowTests: '--allow-tests',
|
|
19
19
|
contextLines: '--context',
|
|
20
20
|
format: '--format',
|
|
21
|
-
inputFile: '--input-file'
|
|
21
|
+
inputFile: '--input-file',
|
|
22
|
+
lsp: '--lsp'
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -31,7 +32,8 @@ const EXTRACT_FLAG_MAP = {
|
|
|
31
32
|
* @param {string} [options.cwd] - Working directory for resolving relative file paths
|
|
32
33
|
* @param {boolean} [options.allowTests] - Include test files
|
|
33
34
|
* @param {number} [options.contextLines] - Number of context lines to include
|
|
34
|
-
* @param {string} [options.format] - Output format ('markdown', 'plain', 'json'
|
|
35
|
+
* @param {string} [options.format] - Output format ('markdown', 'plain', 'json')
|
|
36
|
+
* @param {boolean} [options.lsp] - Use LSP (Language Server Protocol) for call hierarchy and reference graphs
|
|
35
37
|
* @param {Object} [options.binaryOptions] - Options for getting the binary
|
|
36
38
|
* @param {boolean} [options.binaryOptions.forceDownload] - Force download even if binary exists
|
|
37
39
|
* @param {string} [options.binaryOptions.version] - Specific version to download
|