@probelabs/probe 0.6.0-rc279 → 0.6.0-rc280
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/binaries/probe-v0.6.0-rc280-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc280-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc280-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc280-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc280-x86_64-unknown-linux-musl.tar.gz +0 -0
- package/build/agent/ProbeAgent.js +51 -26
- package/build/agent/dsl/environment.js +1 -0
- package/build/agent/index.js +150 -74
- package/build/delegate.js +22 -12
- package/build/downloader.js +28 -25
- package/build/tools/analyzeAll.js +2 -6
- package/build/tools/common.js +4 -3
- package/build/tools/vercel.js +65 -6
- package/cjs/agent/ProbeAgent.cjs +150 -74
- package/cjs/index.cjs +150 -74
- package/package.json +1 -1
- package/src/agent/ProbeAgent.js +51 -26
- package/src/agent/dsl/environment.js +1 -0
- package/src/delegate.js +22 -12
- package/src/downloader.js +28 -25
- package/src/tools/analyzeAll.js +2 -6
- package/src/tools/common.js +4 -3
- package/src/tools/vercel.js +65 -6
- package/bin/binaries/probe-v0.6.0-rc279-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc279-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc279-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc279-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc279-x86_64-unknown-linux-musl.tar.gz +0 -0
package/build/agent/index.js
CHANGED
|
@@ -2293,9 +2293,7 @@ async function acquireFileLock(lockPath, version2) {
|
|
|
2293
2293
|
};
|
|
2294
2294
|
try {
|
|
2295
2295
|
await fs3.writeFile(lockPath, JSON.stringify(lockData), { flag: "wx" });
|
|
2296
|
-
|
|
2297
|
-
console.log(`Acquired file lock: ${lockPath}`);
|
|
2298
|
-
}
|
|
2296
|
+
console.log(`Acquired file lock: ${lockPath}`);
|
|
2299
2297
|
return true;
|
|
2300
2298
|
} catch (error) {
|
|
2301
2299
|
if (error.code === "EEXIST") {
|
|
@@ -2303,15 +2301,11 @@ async function acquireFileLock(lockPath, version2) {
|
|
|
2303
2301
|
const existingLock = JSON.parse(await fs3.readFile(lockPath, "utf-8"));
|
|
2304
2302
|
const lockAge = Date.now() - existingLock.timestamp;
|
|
2305
2303
|
if (lockAge > LOCK_TIMEOUT_MS) {
|
|
2306
|
-
|
|
2307
|
-
console.log(`Removing stale lock file (age: ${Math.round(lockAge / 1e3)}s, pid: ${existingLock.pid})`);
|
|
2308
|
-
}
|
|
2304
|
+
console.log(`Removing stale lock file (age: ${Math.round(lockAge / 1e3)}s, pid: ${existingLock.pid})`);
|
|
2309
2305
|
await fs3.remove(lockPath);
|
|
2310
2306
|
return false;
|
|
2311
2307
|
}
|
|
2312
|
-
|
|
2313
|
-
console.log(`Download in progress by process ${existingLock.pid}, waiting...`);
|
|
2314
|
-
}
|
|
2308
|
+
console.log(`Download in progress by process ${existingLock.pid}, waiting...`);
|
|
2315
2309
|
return false;
|
|
2316
2310
|
} catch (readError) {
|
|
2317
2311
|
if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
|
|
@@ -2352,36 +2346,36 @@ async function releaseFileLock(lockPath) {
|
|
|
2352
2346
|
}
|
|
2353
2347
|
async function waitForFileLock(lockPath, binaryPath) {
|
|
2354
2348
|
const startTime = Date.now();
|
|
2349
|
+
let lastStatusTime = startTime;
|
|
2350
|
+
console.log(`Waiting for file lock to clear: ${lockPath}`);
|
|
2355
2351
|
while (Date.now() - startTime < MAX_LOCK_WAIT_MS) {
|
|
2356
2352
|
if (await fs3.pathExists(binaryPath)) {
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
}
|
|
2353
|
+
const waitedSeconds = Math.round((Date.now() - startTime) / 1e3);
|
|
2354
|
+
console.log(`Binary now available at ${binaryPath}, download completed by another process (waited ${waitedSeconds}s)`);
|
|
2360
2355
|
return true;
|
|
2361
2356
|
}
|
|
2362
2357
|
const lockExists = await fs3.pathExists(lockPath);
|
|
2363
2358
|
if (!lockExists) {
|
|
2364
|
-
|
|
2365
|
-
console.log(`Lock file removed but binary not found - download may have failed`);
|
|
2366
|
-
}
|
|
2359
|
+
console.log(`Lock file removed but binary not found - download may have failed`);
|
|
2367
2360
|
return false;
|
|
2368
2361
|
}
|
|
2369
2362
|
try {
|
|
2370
2363
|
const lockData = JSON.parse(await fs3.readFile(lockPath, "utf-8"));
|
|
2371
2364
|
const lockAge = Date.now() - lockData.timestamp;
|
|
2372
2365
|
if (lockAge > LOCK_TIMEOUT_MS) {
|
|
2373
|
-
|
|
2374
|
-
console.log(`Lock expired (age: ${Math.round(lockAge / 1e3)}s), will retry download`);
|
|
2375
|
-
}
|
|
2366
|
+
console.log(`Lock expired (age: ${Math.round(lockAge / 1e3)}s), will retry download`);
|
|
2376
2367
|
return false;
|
|
2377
2368
|
}
|
|
2378
2369
|
} catch {
|
|
2379
2370
|
}
|
|
2371
|
+
if (Date.now() - lastStatusTime >= 15e3) {
|
|
2372
|
+
const elapsedSeconds = Math.round((Date.now() - startTime) / 1e3);
|
|
2373
|
+
console.log(`Still waiting for file lock (${elapsedSeconds}s/${MAX_LOCK_WAIT_MS / 1e3}s max)`);
|
|
2374
|
+
lastStatusTime = Date.now();
|
|
2375
|
+
}
|
|
2380
2376
|
await new Promise((resolve9) => setTimeout(resolve9, LOCK_POLL_INTERVAL_MS));
|
|
2381
2377
|
}
|
|
2382
|
-
|
|
2383
|
-
console.log(`Timeout waiting for file lock`);
|
|
2384
|
-
}
|
|
2378
|
+
console.log(`Timeout waiting for file lock after ${MAX_LOCK_WAIT_MS / 1e3}s`);
|
|
2385
2379
|
return false;
|
|
2386
2380
|
}
|
|
2387
2381
|
async function withDownloadLock(version2, downloadFn) {
|
|
@@ -2395,9 +2389,7 @@ async function withDownloadLock(version2, downloadFn) {
|
|
|
2395
2389
|
}
|
|
2396
2390
|
downloadLocks.delete(lockKey);
|
|
2397
2391
|
} else {
|
|
2398
|
-
|
|
2399
|
-
console.log(`Download already in progress in this process for version ${lockKey}, waiting...`);
|
|
2400
|
-
}
|
|
2392
|
+
console.log(`Download already in progress in this process for version ${lockKey}, waiting...`);
|
|
2401
2393
|
try {
|
|
2402
2394
|
return await lock.promise;
|
|
2403
2395
|
} catch (error) {
|
|
@@ -2407,10 +2399,16 @@ async function withDownloadLock(version2, downloadFn) {
|
|
|
2407
2399
|
}
|
|
2408
2400
|
}
|
|
2409
2401
|
}
|
|
2402
|
+
let timeoutId = null;
|
|
2410
2403
|
const downloadPromise = Promise.race([
|
|
2411
2404
|
downloadFn(),
|
|
2412
2405
|
new Promise(
|
|
2413
|
-
(_, reject2) =>
|
|
2406
|
+
(_, reject2) => {
|
|
2407
|
+
timeoutId = setTimeout(() => reject2(new Error(`Download timeout after ${LOCK_TIMEOUT_MS / 1e3}s`)), LOCK_TIMEOUT_MS);
|
|
2408
|
+
if (timeoutId.unref) {
|
|
2409
|
+
timeoutId.unref();
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2414
2412
|
)
|
|
2415
2413
|
]);
|
|
2416
2414
|
downloadLocks.set(lockKey, {
|
|
@@ -2421,6 +2419,9 @@ async function withDownloadLock(version2, downloadFn) {
|
|
|
2421
2419
|
const result = await downloadPromise;
|
|
2422
2420
|
return result;
|
|
2423
2421
|
} finally {
|
|
2422
|
+
if (timeoutId) {
|
|
2423
|
+
clearTimeout(timeoutId);
|
|
2424
|
+
}
|
|
2424
2425
|
downloadLocks.delete(lockKey);
|
|
2425
2426
|
}
|
|
2426
2427
|
}
|
|
@@ -4142,10 +4143,9 @@ var init_delegate = __esm({
|
|
|
4142
4143
|
if (this.tryAcquire(parentSessionId)) {
|
|
4143
4144
|
return true;
|
|
4144
4145
|
}
|
|
4145
|
-
|
|
4146
|
-
console.error(`[DelegationManager] Slot unavailable (${this.globalActive}/${this.maxConcurrent}), queuing... (queue size: ${this.waitQueue.length}, timeout: ${effectiveTimeout}ms)`);
|
|
4147
|
-
}
|
|
4146
|
+
console.error(`[DelegationManager] Slot unavailable (${this.globalActive}/${this.maxConcurrent}), queuing... (queue size: ${this.waitQueue.length + 1}, timeout: ${effectiveTimeout}ms)`);
|
|
4148
4147
|
return new Promise((resolve9, reject2) => {
|
|
4148
|
+
const queuedAt = Date.now();
|
|
4149
4149
|
const entry = {
|
|
4150
4150
|
resolve: null,
|
|
4151
4151
|
// Will be wrapped below
|
|
@@ -4153,20 +4153,23 @@ var init_delegate = __esm({
|
|
|
4153
4153
|
// Will be wrapped below
|
|
4154
4154
|
parentSessionId,
|
|
4155
4155
|
debug,
|
|
4156
|
-
queuedAt
|
|
4157
|
-
timeoutId: null
|
|
4156
|
+
queuedAt,
|
|
4157
|
+
timeoutId: null,
|
|
4158
|
+
reminderId: null
|
|
4158
4159
|
};
|
|
4159
4160
|
let settled = false;
|
|
4160
4161
|
entry.resolve = (value) => {
|
|
4161
4162
|
if (settled) return;
|
|
4162
4163
|
settled = true;
|
|
4163
4164
|
if (entry.timeoutId) clearTimeout(entry.timeoutId);
|
|
4165
|
+
if (entry.reminderId) clearInterval(entry.reminderId);
|
|
4164
4166
|
resolve9(value);
|
|
4165
4167
|
};
|
|
4166
4168
|
entry.reject = (error) => {
|
|
4167
4169
|
if (settled) return;
|
|
4168
4170
|
settled = true;
|
|
4169
4171
|
if (entry.timeoutId) clearTimeout(entry.timeoutId);
|
|
4172
|
+
if (entry.reminderId) clearInterval(entry.reminderId);
|
|
4170
4173
|
reject2(error);
|
|
4171
4174
|
};
|
|
4172
4175
|
if (effectiveTimeout > 0) {
|
|
@@ -4178,6 +4181,13 @@ var init_delegate = __esm({
|
|
|
4178
4181
|
entry.reject(new Error(`Delegation queue timeout: waited ${effectiveTimeout}ms for an available slot`));
|
|
4179
4182
|
}, effectiveTimeout);
|
|
4180
4183
|
}
|
|
4184
|
+
entry.reminderId = setInterval(() => {
|
|
4185
|
+
const waitedSeconds = Math.round((Date.now() - queuedAt) / 1e3);
|
|
4186
|
+
console.error(`[DelegationManager] Still waiting for slot (${waitedSeconds}s). ${this.globalActive}/${this.maxConcurrent} active, ${this.waitQueue.length} queued.`);
|
|
4187
|
+
}, 15e3);
|
|
4188
|
+
if (entry.reminderId.unref) {
|
|
4189
|
+
entry.reminderId.unref();
|
|
4190
|
+
}
|
|
4181
4191
|
this.waitQueue.push(entry);
|
|
4182
4192
|
});
|
|
4183
4193
|
}
|
|
@@ -4216,18 +4226,14 @@ var init_delegate = __esm({
|
|
|
4216
4226
|
const sessionData = this.sessionDelegations.get(parentSessionId);
|
|
4217
4227
|
const sessionCount = sessionData?.count || 0;
|
|
4218
4228
|
if (sessionCount >= this.maxPerSession) {
|
|
4219
|
-
|
|
4220
|
-
console.error(`[DelegationManager] Session limit (${this.maxPerSession}) reached for queued item, rejecting`);
|
|
4221
|
-
}
|
|
4229
|
+
console.error(`[DelegationManager] Session limit (${this.maxPerSession}) reached for queued item, rejecting`);
|
|
4222
4230
|
toReject.push({ reject: reject2, error: new Error(`Maximum delegations per session (${this.maxPerSession}) reached for session ${parentSessionId}`) });
|
|
4223
4231
|
continue;
|
|
4224
4232
|
}
|
|
4225
4233
|
}
|
|
4226
4234
|
this._incrementCounters(parentSessionId);
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
console.error(`[DelegationManager] Granted slot from queue (waited ${waitTime}ms). Active: ${this.globalActive}/${this.maxConcurrent}`);
|
|
4230
|
-
}
|
|
4235
|
+
const waitTime = Date.now() - queuedAt;
|
|
4236
|
+
console.error(`[DelegationManager] Granted slot from queue (waited ${waitTime}ms). Active: ${this.globalActive}/${this.maxConcurrent}`);
|
|
4231
4237
|
toResolve.push(resolve9);
|
|
4232
4238
|
}
|
|
4233
4239
|
if (toResolve.length > 0 || toReject.length > 0) {
|
|
@@ -4277,6 +4283,9 @@ var init_delegate = __esm({
|
|
|
4277
4283
|
if (entry.timeoutId) {
|
|
4278
4284
|
clearTimeout(entry.timeoutId);
|
|
4279
4285
|
}
|
|
4286
|
+
if (entry.reminderId) {
|
|
4287
|
+
clearInterval(entry.reminderId);
|
|
4288
|
+
}
|
|
4280
4289
|
if (entry.reject) {
|
|
4281
4290
|
entry.reject(new Error("DelegationManager was cleaned up"));
|
|
4282
4291
|
}
|
|
@@ -4421,16 +4430,12 @@ async function processChunksParallel(chunks, extractionPrompt, maxWorkers, optio
|
|
|
4421
4430
|
return result;
|
|
4422
4431
|
});
|
|
4423
4432
|
active.add(promise);
|
|
4424
|
-
|
|
4425
|
-
console.error(`[analyze_all] Started processing chunk ${chunk.id}/${chunk.total}`);
|
|
4426
|
-
}
|
|
4433
|
+
console.error(`[analyze_all] Started processing chunk ${chunk.id}/${chunk.total}`);
|
|
4427
4434
|
}
|
|
4428
4435
|
if (active.size > 0) {
|
|
4429
4436
|
const result = await Promise.race(active);
|
|
4430
4437
|
results.push(result);
|
|
4431
|
-
|
|
4432
|
-
console.error(`[analyze_all] Completed chunk ${result.chunk.id}/${result.chunk.total}`);
|
|
4433
|
-
}
|
|
4438
|
+
console.error(`[analyze_all] Completed chunk ${result.chunk.id}/${result.chunk.total}`);
|
|
4434
4439
|
}
|
|
4435
4440
|
}
|
|
4436
4441
|
results.sort((a, b) => a.chunk.id - b.chunk.id);
|
|
@@ -8924,13 +8929,13 @@ function resolveTargetPath(target, cwd) {
|
|
|
8924
8929
|
}
|
|
8925
8930
|
return filePart + suffix;
|
|
8926
8931
|
}
|
|
8927
|
-
var searchSchema, searchAllSchema, querySchema, extractSchema, delegateSchema, listSkillsSchema, useSkillSchema, listFilesSchema, searchFilesSchema, readImageSchema, bashSchema, analyzeAllSchema, executePlanSchema, cleanupExecutePlanSchema, searchDescription, queryDescription, extractDescription, delegateDescription, analyzeAllDescription;
|
|
8932
|
+
var searchSchema, searchAllSchema, querySchema, extractSchema, delegateSchema, listSkillsSchema, useSkillSchema, listFilesSchema, searchFilesSchema, readImageSchema, bashSchema, analyzeAllSchema, executePlanSchema, cleanupExecutePlanSchema, searchDescription, searchDelegateDescription, queryDescription, extractDescription, delegateDescription, analyzeAllDescription;
|
|
8928
8933
|
var init_common = __esm({
|
|
8929
8934
|
"src/tools/common.js"() {
|
|
8930
8935
|
"use strict";
|
|
8931
8936
|
init_zod();
|
|
8932
8937
|
searchSchema = external_exports.object({
|
|
8933
|
-
query: external_exports.string().describe("Search query
|
|
8938
|
+
query: external_exports.string().describe("Search query \u2014 natural language questions or Elasticsearch-style keywords both work. For keywords: use quotes for exact phrases, AND/OR for boolean logic, - for negation. Probe handles stemming and camelCase/snake_case splitting automatically, so do NOT try case or style variations of the same keyword."),
|
|
8934
8939
|
path: external_exports.string().optional().default(".").describe('Path to search in. For dependencies use "go:github.com/owner/repo", "js:package_name", or "rust:cargo_name" etc.'),
|
|
8935
8940
|
exact: external_exports.boolean().optional().default(false).describe('Default (false) enables stemming and keyword splitting for exploratory search - "getUserData" matches "get", "user", "data", etc. Set true for precise symbol lookup where "getUserData" matches only "getUserData". Use true when you know the exact symbol name.'),
|
|
8936
8941
|
maxTokens: external_exports.number().nullable().optional().describe("Maximum tokens to return. Default is 20000. Set to null for unlimited results."),
|
|
@@ -8938,7 +8943,7 @@ var init_common = __esm({
|
|
|
8938
8943
|
nextPage: external_exports.boolean().optional().default(false).describe("Set to true when requesting the next page of results. Requires passing the same session ID from the previous search output.")
|
|
8939
8944
|
});
|
|
8940
8945
|
searchAllSchema = external_exports.object({
|
|
8941
|
-
query: external_exports.string().describe("Search query
|
|
8946
|
+
query: external_exports.string().describe("Search query \u2014 natural language questions or Elasticsearch-style keywords both work. For keywords: use quotes for exact phrases, AND/OR for boolean logic, - for negation. Probe handles stemming and camelCase/snake_case splitting automatically, so do NOT try case or style variations of the same keyword."),
|
|
8942
8947
|
path: external_exports.string().optional().default(".").describe("Path to search in."),
|
|
8943
8948
|
exact: external_exports.boolean().optional().default(false).describe("Use exact matching instead of stemming."),
|
|
8944
8949
|
maxTokensPerPage: external_exports.number().optional().default(2e4).describe("Tokens per page when paginating. Default 20000."),
|
|
@@ -8993,7 +8998,8 @@ var init_common = __esm({
|
|
|
8993
8998
|
clearOutputBuffer: external_exports.boolean().optional().default(true).describe("Clear the output buffer from previous execute_plan calls"),
|
|
8994
8999
|
clearSessionStore: external_exports.boolean().optional().default(false).describe("Clear the session store (persisted data across execute_plan calls)")
|
|
8995
9000
|
});
|
|
8996
|
-
searchDescription =
|
|
9001
|
+
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.';
|
|
9002
|
+
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.';
|
|
8997
9003
|
queryDescription = "Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.";
|
|
8998
9004
|
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.";
|
|
8999
9005
|
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.";
|
|
@@ -9178,11 +9184,41 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
|
|
|
9178
9184
|
"- extract: Verify code snippets to ensure targets are actually relevant before including them.",
|
|
9179
9185
|
"- listFiles: Understand directory structure to find where relevant code might live.",
|
|
9180
9186
|
"",
|
|
9181
|
-
"
|
|
9187
|
+
"CRITICAL - How probe search works (do NOT ignore):",
|
|
9188
|
+
"- By default (exact=false), probe ALREADY handles stemming, case-insensitive matching, and camelCase/snake_case splitting.",
|
|
9189
|
+
'- Searching "allowed_ips" ALREADY matches "AllowedIPs", "allowedIps", "allowed_ips", etc. Do NOT manually try case/style variations.',
|
|
9190
|
+
'- Searching "getUserData" ALREADY matches "get", "user", "data" and their variations.',
|
|
9191
|
+
"- NEVER repeat the same search query \u2014 you will get the same results.",
|
|
9192
|
+
"- NEVER search trivial variations of the same keyword (e.g., AllowedIPs then allowedIps then allowed_ips). This is wasteful \u2014 probe handles it.",
|
|
9193
|
+
"- If a search returns no results, the term likely does not exist in that path. Try a genuinely DIFFERENT keyword or concept, not a variation.",
|
|
9194
|
+
"- If 2-3 consecutive searches return no results for a concept, STOP searching for it and move on.",
|
|
9195
|
+
"",
|
|
9196
|
+
"GOOD search strategy (do this):",
|
|
9197
|
+
' Query: "How does authentication work and how are sessions managed?"',
|
|
9198
|
+
' \u2192 search "authentication" \u2192 search "session management" (two different concepts)',
|
|
9199
|
+
' Query: "Find the IP allowlist middleware"',
|
|
9200
|
+
' \u2192 search "allowlist middleware" (one search, probe handles IP/ip/Ip variations)',
|
|
9201
|
+
' Query: "How does BM25 scoring work with SIMD optimization?"',
|
|
9202
|
+
' \u2192 search "BM25 scoring" \u2192 search "SIMD optimization" (two different concepts)',
|
|
9203
|
+
"",
|
|
9204
|
+
"BAD search strategy (never do this):",
|
|
9205
|
+
' \u2192 search "AllowedIPs" \u2192 search "allowedIps" \u2192 search "allowed_ips" (WRONG: these are trivial case variations, probe handles them)',
|
|
9206
|
+
' \u2192 search "CIDR" \u2192 search "cidr" \u2192 search "Cidr" \u2192 search "*cidr*" (WRONG: same keyword repeated with variations)',
|
|
9207
|
+
' \u2192 search "error handling" \u2192 search "error handling" \u2192 search "error handling" (WRONG: repeating exact same query)',
|
|
9208
|
+
"",
|
|
9209
|
+
"Keyword tips:",
|
|
9210
|
+
"- Common programming keywords are filtered as stopwords when unquoted: function, class, return, new, struct, impl, var, let, const, etc.",
|
|
9211
|
+
'- Avoid searching for these alone \u2014 combine with a specific term (e.g., "middleware function" is fine, "function" alone is too generic).',
|
|
9212
|
+
'- To bypass stopword filtering: wrap terms in quotes ("return", "struct") or set exact=true. Both disable stemming and splitting too.',
|
|
9213
|
+
"- Multiple words without operators use OR logic: foo bar = foo OR bar. Use AND explicitly if you need both: foo AND bar.",
|
|
9214
|
+
'- camelCase terms are split: getUserData becomes "get", "user", "data" \u2014 so one search covers all naming styles.',
|
|
9215
|
+
"",
|
|
9216
|
+
"Strategy:",
|
|
9182
9217
|
"1. Analyze the query - identify key concepts, entities, and relationships",
|
|
9183
|
-
|
|
9184
|
-
"3.
|
|
9185
|
-
"4.
|
|
9218
|
+
"2. Run ONE focused search per concept with the most natural keyword. Trust probe to handle variations.",
|
|
9219
|
+
"3. If a search returns results, use extract to verify relevance",
|
|
9220
|
+
"4. Only try a different keyword if the first one returned irrelevant results (not if it returned no results \u2014 that means the concept is absent)",
|
|
9221
|
+
"5. Combine all relevant targets in your final response",
|
|
9186
9222
|
"",
|
|
9187
9223
|
`Query: ${searchQuery}`,
|
|
9188
9224
|
`Search path(s): ${searchPath}`,
|
|
@@ -9233,9 +9269,12 @@ var init_vercel = __esm({
|
|
|
9233
9269
|
}
|
|
9234
9270
|
return result;
|
|
9235
9271
|
};
|
|
9272
|
+
const previousSearches = /* @__PURE__ */ new Set();
|
|
9273
|
+
const paginationCounts = /* @__PURE__ */ new Map();
|
|
9274
|
+
const MAX_PAGES_PER_QUERY = 3;
|
|
9236
9275
|
return tool({
|
|
9237
9276
|
name: "search",
|
|
9238
|
-
description: searchDelegate ?
|
|
9277
|
+
description: searchDelegate ? searchDelegateDescription : searchDescription,
|
|
9239
9278
|
inputSchema: searchSchema,
|
|
9240
9279
|
execute: async ({ query: searchQuery, path: path9, allow_tests, exact, maxTokens: paramMaxTokens, language, session, nextPage }) => {
|
|
9241
9280
|
const effectiveMaxTokens = paramMaxTokens || maxTokens;
|
|
@@ -9273,6 +9312,26 @@ var init_vercel = __esm({
|
|
|
9273
9312
|
return await search(searchOptions);
|
|
9274
9313
|
};
|
|
9275
9314
|
if (!searchDelegate) {
|
|
9315
|
+
const searchKey = `${searchQuery}::${searchPath}::${exact || false}`;
|
|
9316
|
+
if (!nextPage) {
|
|
9317
|
+
if (previousSearches.has(searchKey)) {
|
|
9318
|
+
if (debug) {
|
|
9319
|
+
console.error(`[DEDUP] Blocked duplicate search: "${searchQuery}" in "${searchPath}"`);
|
|
9320
|
+
}
|
|
9321
|
+
return "DUPLICATE SEARCH BLOCKED: You already searched for this exact query in this path. Do NOT repeat the same search. If you need more results, set nextPage=true with the session ID from the previous search. Otherwise, try a genuinely different keyword, use extract to examine results you already found, or use attempt_completion if you have enough information.";
|
|
9322
|
+
}
|
|
9323
|
+
previousSearches.add(searchKey);
|
|
9324
|
+
paginationCounts.set(searchKey, 0);
|
|
9325
|
+
} else {
|
|
9326
|
+
const pageCount = (paginationCounts.get(searchKey) || 0) + 1;
|
|
9327
|
+
paginationCounts.set(searchKey, pageCount);
|
|
9328
|
+
if (pageCount > MAX_PAGES_PER_QUERY) {
|
|
9329
|
+
if (debug) {
|
|
9330
|
+
console.error(`[DEDUP] Blocked excessive pagination (page ${pageCount}/${MAX_PAGES_PER_QUERY}): "${searchQuery}" in "${searchPath}"`);
|
|
9331
|
+
}
|
|
9332
|
+
return `PAGINATION LIMIT REACHED: You have already retrieved ${MAX_PAGES_PER_QUERY} pages of results for this query. You have enough results \u2014 use extract to examine specific files, or use attempt_completion to return your findings.`;
|
|
9333
|
+
}
|
|
9334
|
+
}
|
|
9276
9335
|
try {
|
|
9277
9336
|
const result = maybeAnnotate(await runRawSearch());
|
|
9278
9337
|
if (options.fileTracker && typeof result === "string") {
|
|
@@ -22143,6 +22202,7 @@ function generateSandboxGlobals(options) {
|
|
|
22143
22202
|
executing.add(p);
|
|
22144
22203
|
results.push(p);
|
|
22145
22204
|
if (executing.size >= mapConcurrency) {
|
|
22205
|
+
console.error(`[map] Concurrency limit reached (${executing.size}/${mapConcurrency}), waiting for a slot...`);
|
|
22146
22206
|
await Promise.race(executing);
|
|
22147
22207
|
}
|
|
22148
22208
|
}
|
|
@@ -81970,6 +82030,7 @@ var init_ProbeAgent = __esm({
|
|
|
81970
82030
|
this.completionPrompt = options.completionPrompt || null;
|
|
81971
82031
|
this.thinkingEffort = options.thinkingEffort || null;
|
|
81972
82032
|
const effectiveAllowedTools = options.disableTools ? [] : options.allowedTools;
|
|
82033
|
+
this._rawAllowedTools = options.allowedTools;
|
|
81973
82034
|
this.allowedTools = this._parseAllowedTools(effectiveAllowedTools);
|
|
81974
82035
|
this.storageAdapter = options.storageAdapter || new InMemoryStorageAdapter();
|
|
81975
82036
|
this.hooks = new HookManager();
|
|
@@ -82112,6 +82173,16 @@ var init_ProbeAgent = __esm({
|
|
|
82112
82173
|
_filterMcpTools(mcpToolNames) {
|
|
82113
82174
|
return mcpToolNames.filter((toolName) => this._isMcpToolAllowed(toolName));
|
|
82114
82175
|
}
|
|
82176
|
+
/**
|
|
82177
|
+
* Check if query tool was explicitly listed in allowedTools (not via wildcard).
|
|
82178
|
+
* Query (ast-grep) is excluded by default because models struggle with AST pattern syntax.
|
|
82179
|
+
* @returns {boolean}
|
|
82180
|
+
* @private
|
|
82181
|
+
*/
|
|
82182
|
+
_isQueryExplicitlyAllowed() {
|
|
82183
|
+
if (!this._rawAllowedTools) return false;
|
|
82184
|
+
return Array.isArray(this._rawAllowedTools) && this._rawAllowedTools.includes("query");
|
|
82185
|
+
}
|
|
82115
82186
|
/**
|
|
82116
82187
|
* Check if tracer is AppTracer (expects sessionId as first param) vs SimpleAppTracer
|
|
82117
82188
|
* @returns {boolean} - True if tracer is AppTracer style (requires sessionId)
|
|
@@ -82405,7 +82476,7 @@ var init_ProbeAgent = __esm({
|
|
|
82405
82476
|
if (wrappedTools.searchToolInstance && isToolAllowed("search")) {
|
|
82406
82477
|
this.toolImplementations.search = wrappedTools.searchToolInstance;
|
|
82407
82478
|
}
|
|
82408
|
-
if (wrappedTools.queryToolInstance && isToolAllowed("query")) {
|
|
82479
|
+
if (wrappedTools.queryToolInstance && isToolAllowed("query") && this._isQueryExplicitlyAllowed()) {
|
|
82409
82480
|
this.toolImplementations.query = wrappedTools.queryToolInstance;
|
|
82410
82481
|
}
|
|
82411
82482
|
if (wrappedTools.extractToolInstance && isToolAllowed("extract")) {
|
|
@@ -83344,12 +83415,13 @@ var init_ProbeAgent = __esm({
|
|
|
83344
83415
|
const toolMap = {
|
|
83345
83416
|
search: {
|
|
83346
83417
|
schema: searchSchema,
|
|
83347
|
-
description: "Search code in the repository using keyword queries with Elasticsearch syntax."
|
|
83348
|
-
},
|
|
83349
|
-
query: {
|
|
83350
|
-
schema: querySchema,
|
|
83351
|
-
description: "Search code using ast-grep structural pattern matching."
|
|
83418
|
+
description: this.searchDelegate ? "Search code in the repository by asking a question. Accepts natural language questions \u2014 a subagent breaks them into targeted keyword searches and returns extracted code blocks. Do NOT formulate keyword queries yourself." : "Search code in the repository using keyword queries with Elasticsearch syntax. Handles stemming, case-insensitive matching, and camelCase/snake_case splitting automatically \u2014 do NOT try keyword variations manually."
|
|
83352
83419
|
},
|
|
83420
|
+
// query tool (ast-grep) removed from AI-facing tools — models struggle with pattern syntax
|
|
83421
|
+
// query: {
|
|
83422
|
+
// schema: querySchema,
|
|
83423
|
+
// description: 'Search code using ast-grep structural pattern matching.'
|
|
83424
|
+
// },
|
|
83353
83425
|
extract: {
|
|
83354
83426
|
schema: extractSchema,
|
|
83355
83427
|
description: "Extract code blocks from files based on file paths and optional line numbers."
|
|
@@ -84017,25 +84089,27 @@ ${this.architectureContext.content}
|
|
|
84017
84089
|
} else {
|
|
84018
84090
|
systemPrompt += predefinedPrompts["code-explorer"] + "\n\n";
|
|
84019
84091
|
}
|
|
84092
|
+
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.";
|
|
84020
84093
|
systemPrompt += `You have access to powerful code search and analysis tools through MCP:
|
|
84021
|
-
|
|
84094
|
+
${searchToolDesc1}
|
|
84022
84095
|
- extract: Extract specific code sections with context
|
|
84023
|
-
- query: Use AST patterns for structural code matching
|
|
84024
84096
|
- listFiles: Browse directory contents
|
|
84025
84097
|
- searchFiles: Find files by name patterns`;
|
|
84026
84098
|
if (this.enableBash) {
|
|
84027
84099
|
systemPrompt += `
|
|
84028
84100
|
- bash: Execute bash commands for system operations`;
|
|
84029
84101
|
}
|
|
84030
|
-
const
|
|
84031
|
-
const
|
|
84102
|
+
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.";
|
|
84103
|
+
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";
|
|
84032
84104
|
systemPrompt += `
|
|
84033
84105
|
|
|
84034
84106
|
When exploring code:
|
|
84035
|
-
${
|
|
84036
|
-
${
|
|
84107
|
+
${searchGuidance1}
|
|
84108
|
+
${extractGuidance1}
|
|
84037
84109
|
3. Prefer focused, specific searches over broad queries
|
|
84038
|
-
4.
|
|
84110
|
+
4. Do NOT repeat the same search or try trivial keyword variations \u2014 probe handles stemming and case variations automatically
|
|
84111
|
+
5. If 2-3 consecutive searches return no results for a concept, stop searching for it \u2014 the term likely does not exist in that codebase
|
|
84112
|
+
6. Combine multiple tools to build complete understanding`;
|
|
84039
84113
|
if (this.allowedFolders && this.allowedFolders.length > 0) {
|
|
84040
84114
|
systemPrompt += `
|
|
84041
84115
|
|
|
@@ -84070,25 +84144,27 @@ Workspace: ${this.allowedFolders.join(", ")}`;
|
|
|
84070
84144
|
} else {
|
|
84071
84145
|
systemPrompt += predefinedPrompts["code-explorer"] + "\n\n";
|
|
84072
84146
|
}
|
|
84147
|
+
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.";
|
|
84073
84148
|
systemPrompt += `You have access to powerful code search and analysis tools through MCP:
|
|
84074
|
-
|
|
84149
|
+
${searchToolDesc2}
|
|
84075
84150
|
- extract: Extract specific code sections with context
|
|
84076
|
-
- query: Use AST patterns for structural code matching
|
|
84077
84151
|
- listFiles: Browse directory contents
|
|
84078
84152
|
- searchFiles: Find files by name patterns`;
|
|
84079
84153
|
if (this.enableBash) {
|
|
84080
84154
|
systemPrompt += `
|
|
84081
84155
|
- bash: Execute bash commands for system operations`;
|
|
84082
84156
|
}
|
|
84083
|
-
const
|
|
84084
|
-
const
|
|
84157
|
+
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.";
|
|
84158
|
+
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";
|
|
84085
84159
|
systemPrompt += `
|
|
84086
84160
|
|
|
84087
84161
|
When exploring code:
|
|
84088
|
-
${
|
|
84089
|
-
${
|
|
84162
|
+
${searchGuidance2}
|
|
84163
|
+
${extractGuidance2}
|
|
84090
84164
|
3. Prefer focused, specific searches over broad queries
|
|
84091
|
-
4.
|
|
84165
|
+
4. Do NOT repeat the same search or try trivial keyword variations \u2014 probe handles stemming and case variations automatically
|
|
84166
|
+
5. If 2-3 consecutive searches return no results for a concept, stop searching for it \u2014 the term likely does not exist in that codebase
|
|
84167
|
+
6. Combine multiple tools to build complete understanding`;
|
|
84092
84168
|
if (this.allowedFolders && this.allowedFolders.length > 0) {
|
|
84093
84169
|
systemPrompt += `
|
|
84094
84170
|
|
|
@@ -84139,10 +84215,10 @@ Workspace: ${this.allowedFolders.join(", ")}`;
|
|
|
84139
84215
|
Follow these instructions carefully:
|
|
84140
84216
|
1. Analyze the user's request.
|
|
84141
84217
|
2. Use the available tools step-by-step to fulfill the request.
|
|
84142
|
-
3. You should always prefer the search tool for code-related questions.${this.searchDelegate ? "
|
|
84218
|
+
3. You should always prefer the search tool for code-related questions.${this.searchDelegate ? " Ask natural language questions \u2014 the search subagent handles keyword formulation and returns extracted code blocks. Use extract only to expand context or read full files." : " Search handles stemming and case variations automatically \u2014 do NOT try keyword variations manually. Read full files only if really necessary."}
|
|
84143
84219
|
4. Ensure to get really deep and understand the full picture before answering.
|
|
84144
84220
|
5. Once the task is fully completed, use the attempt_completion tool to provide the final result.
|
|
84145
|
-
6. Prefer concise and focused search queries. Use specific keywords and phrases to narrow down results
|
|
84221
|
+
6. ${this.searchDelegate ? "Ask clear, specific questions when searching. Each search should target a distinct concept or question." : "Prefer concise and focused search queries. Use specific keywords and phrases to narrow down results."}${this.allowEdit ? `
|
|
84146
84222
|
7. When modifying files, choose the appropriate tool:
|
|
84147
84223
|
- Use 'edit' for all code modifications:
|
|
84148
84224
|
* PREFERRED: Use start_line (and optionally end_line) for line-targeted editing \u2014 this is the safest and most precise approach.${this.hashLines ? ' Use the line:hash references from extract/search output (e.g. "42:ab") for integrity verification.' : ""} Always use extract first to see line numbers${this.hashLines ? " and hashes" : ""}, then edit by line reference.
|
package/build/delegate.js
CHANGED
|
@@ -122,20 +122,20 @@ class DelegationManager {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
// Need to wait in queue
|
|
125
|
-
|
|
126
|
-
console.error(`[DelegationManager] Slot unavailable (${this.globalActive}/${this.maxConcurrent}), queuing... (queue size: ${this.waitQueue.length}, timeout: ${effectiveTimeout}ms)`);
|
|
127
|
-
}
|
|
125
|
+
console.error(`[DelegationManager] Slot unavailable (${this.globalActive}/${this.maxConcurrent}), queuing... (queue size: ${this.waitQueue.length + 1}, timeout: ${effectiveTimeout}ms)`);
|
|
128
126
|
|
|
129
127
|
// Create a promise that will be resolved when a slot becomes available
|
|
130
128
|
// or rejected if session limit is exceeded or queue timeout expires
|
|
131
129
|
return new Promise((resolve, reject) => {
|
|
130
|
+
const queuedAt = Date.now();
|
|
132
131
|
const entry = {
|
|
133
132
|
resolve: null, // Will be wrapped below
|
|
134
133
|
reject: null, // Will be wrapped below
|
|
135
134
|
parentSessionId,
|
|
136
135
|
debug,
|
|
137
|
-
queuedAt
|
|
138
|
-
timeoutId: null
|
|
136
|
+
queuedAt,
|
|
137
|
+
timeoutId: null,
|
|
138
|
+
reminderId: null
|
|
139
139
|
};
|
|
140
140
|
|
|
141
141
|
// Wrap resolve/reject to clear timeout and prevent double-settling
|
|
@@ -144,12 +144,14 @@ class DelegationManager {
|
|
|
144
144
|
if (settled) return;
|
|
145
145
|
settled = true;
|
|
146
146
|
if (entry.timeoutId) clearTimeout(entry.timeoutId);
|
|
147
|
+
if (entry.reminderId) clearInterval(entry.reminderId);
|
|
147
148
|
resolve(value);
|
|
148
149
|
};
|
|
149
150
|
entry.reject = (error) => {
|
|
150
151
|
if (settled) return;
|
|
151
152
|
settled = true;
|
|
152
153
|
if (entry.timeoutId) clearTimeout(entry.timeoutId);
|
|
154
|
+
if (entry.reminderId) clearInterval(entry.reminderId);
|
|
153
155
|
reject(error);
|
|
154
156
|
};
|
|
155
157
|
|
|
@@ -165,6 +167,15 @@ class DelegationManager {
|
|
|
165
167
|
}, effectiveTimeout);
|
|
166
168
|
}
|
|
167
169
|
|
|
170
|
+
// Always emit periodic wait visibility while queued.
|
|
171
|
+
entry.reminderId = setInterval(() => {
|
|
172
|
+
const waitedSeconds = Math.round((Date.now() - queuedAt) / 1000);
|
|
173
|
+
console.error(`[DelegationManager] Still waiting for slot (${waitedSeconds}s). ${this.globalActive}/${this.maxConcurrent} active, ${this.waitQueue.length} queued.`);
|
|
174
|
+
}, 15000);
|
|
175
|
+
if (entry.reminderId.unref) {
|
|
176
|
+
entry.reminderId.unref();
|
|
177
|
+
}
|
|
178
|
+
|
|
168
179
|
this.waitQueue.push(entry);
|
|
169
180
|
});
|
|
170
181
|
}
|
|
@@ -221,9 +232,7 @@ class DelegationManager {
|
|
|
221
232
|
if (sessionCount >= this.maxPerSession) {
|
|
222
233
|
// Session limit reached - reject with error (consistent with tryAcquire behavior)
|
|
223
234
|
// This is a hard limit, not something that will resolve by waiting longer
|
|
224
|
-
|
|
225
|
-
console.error(`[DelegationManager] Session limit (${this.maxPerSession}) reached for queued item, rejecting`);
|
|
226
|
-
}
|
|
235
|
+
console.error(`[DelegationManager] Session limit (${this.maxPerSession}) reached for queued item, rejecting`);
|
|
227
236
|
toReject.push({ reject, error: new Error(`Maximum delegations per session (${this.maxPerSession}) reached for session ${parentSessionId}`) });
|
|
228
237
|
// Continue to process next item in queue
|
|
229
238
|
continue;
|
|
@@ -233,10 +242,8 @@ class DelegationManager {
|
|
|
233
242
|
// Grant the slot
|
|
234
243
|
this._incrementCounters(parentSessionId);
|
|
235
244
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
console.error(`[DelegationManager] Granted slot from queue (waited ${waitTime}ms). Active: ${this.globalActive}/${this.maxConcurrent}`);
|
|
239
|
-
}
|
|
245
|
+
const waitTime = Date.now() - queuedAt;
|
|
246
|
+
console.error(`[DelegationManager] Granted slot from queue (waited ${waitTime}ms). Active: ${this.globalActive}/${this.maxConcurrent}`);
|
|
240
247
|
|
|
241
248
|
toResolve.push(resolve);
|
|
242
249
|
}
|
|
@@ -296,6 +303,9 @@ class DelegationManager {
|
|
|
296
303
|
if (entry.timeoutId) {
|
|
297
304
|
clearTimeout(entry.timeoutId);
|
|
298
305
|
}
|
|
306
|
+
if (entry.reminderId) {
|
|
307
|
+
clearInterval(entry.reminderId);
|
|
308
|
+
}
|
|
299
309
|
// Reject pending entries so they don't hang
|
|
300
310
|
if (entry.reject) {
|
|
301
311
|
entry.reject(new Error('DelegationManager was cleaned up'));
|