@probelabs/probe 0.6.0-rc278 → 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.
@@ -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
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
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
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
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
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
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
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
2358
- console.log(`Binary now available at ${binaryPath}, download completed by another process`);
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
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
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
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
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
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
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
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
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) => setTimeout(() => reject2(new Error(`Download timeout after ${LOCK_TIMEOUT_MS / 1e3}s`)), LOCK_TIMEOUT_MS)
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
  }
@@ -3862,12 +3863,17 @@ async function delegate({
3862
3863
  mcpConfigPath = null,
3863
3864
  delegationManager = null,
3864
3865
  // Optional per-instance manager, falls back to default singleton
3865
- concurrencyLimiter = null
3866
+ concurrencyLimiter = null,
3866
3867
  // Optional global AI concurrency limiter
3868
+ parentAbortSignal = null
3869
+ // Optional AbortSignal from parent to cancel this delegation
3867
3870
  }) {
3868
3871
  if (!task || typeof task !== "string") {
3869
3872
  throw new Error("Task parameter is required and must be a string");
3870
3873
  }
3874
+ if (parentAbortSignal?.aborted) {
3875
+ throw new Error("Delegation cancelled: parent operation was aborted");
3876
+ }
3871
3877
  const hasExplicitTimeout = Object.prototype.hasOwnProperty.call(arguments?.[0] ?? {}, "timeout");
3872
3878
  if (!hasExplicitTimeout) {
3873
3879
  const envTimeoutMs = parseInt(process.env.DELEGATION_TIMEOUT_MS || "", 10);
@@ -3952,12 +3958,37 @@ async function delegate({
3952
3958
  }
3953
3959
  const timeoutPromise = new Promise((_, reject2) => {
3954
3960
  timeoutId = setTimeout(() => {
3961
+ subagent.cancel();
3955
3962
  reject2(new Error(`Delegation timed out after ${timeout} seconds`));
3956
3963
  }, timeout * 1e3);
3957
3964
  });
3965
+ let parentAbortHandler;
3966
+ const parentAbortPromise = new Promise((_, reject2) => {
3967
+ if (parentAbortSignal) {
3968
+ if (parentAbortSignal.aborted) {
3969
+ subagent.cancel();
3970
+ reject2(new Error("Delegation cancelled: parent operation was aborted"));
3971
+ return;
3972
+ }
3973
+ parentAbortHandler = () => {
3974
+ subagent.cancel();
3975
+ reject2(new Error("Delegation cancelled: parent operation was aborted"));
3976
+ };
3977
+ parentAbortSignal.addEventListener("abort", parentAbortHandler, { once: true });
3978
+ }
3979
+ });
3958
3980
  const answerOptions = schema ? { schema } : void 0;
3959
3981
  const answerPromise = answerOptions ? subagent.answer(task, [], answerOptions) : subagent.answer(task);
3960
- const response = await Promise.race([answerPromise, timeoutPromise]);
3982
+ const racers = [answerPromise, timeoutPromise];
3983
+ if (parentAbortSignal) racers.push(parentAbortPromise);
3984
+ let response;
3985
+ try {
3986
+ response = await Promise.race(racers);
3987
+ } finally {
3988
+ if (parentAbortHandler && parentAbortSignal) {
3989
+ parentAbortSignal.removeEventListener("abort", parentAbortHandler);
3990
+ }
3991
+ }
3961
3992
  if (timeoutId !== null) {
3962
3993
  clearTimeout(timeoutId);
3963
3994
  timeoutId = null;
@@ -4112,10 +4143,9 @@ var init_delegate = __esm({
4112
4143
  if (this.tryAcquire(parentSessionId)) {
4113
4144
  return true;
4114
4145
  }
4115
- if (debug) {
4116
- console.error(`[DelegationManager] Slot unavailable (${this.globalActive}/${this.maxConcurrent}), queuing... (queue size: ${this.waitQueue.length}, timeout: ${effectiveTimeout}ms)`);
4117
- }
4146
+ console.error(`[DelegationManager] Slot unavailable (${this.globalActive}/${this.maxConcurrent}), queuing... (queue size: ${this.waitQueue.length + 1}, timeout: ${effectiveTimeout}ms)`);
4118
4147
  return new Promise((resolve9, reject2) => {
4148
+ const queuedAt = Date.now();
4119
4149
  const entry = {
4120
4150
  resolve: null,
4121
4151
  // Will be wrapped below
@@ -4123,20 +4153,23 @@ var init_delegate = __esm({
4123
4153
  // Will be wrapped below
4124
4154
  parentSessionId,
4125
4155
  debug,
4126
- queuedAt: Date.now(),
4127
- timeoutId: null
4156
+ queuedAt,
4157
+ timeoutId: null,
4158
+ reminderId: null
4128
4159
  };
4129
4160
  let settled = false;
4130
4161
  entry.resolve = (value) => {
4131
4162
  if (settled) return;
4132
4163
  settled = true;
4133
4164
  if (entry.timeoutId) clearTimeout(entry.timeoutId);
4165
+ if (entry.reminderId) clearInterval(entry.reminderId);
4134
4166
  resolve9(value);
4135
4167
  };
4136
4168
  entry.reject = (error) => {
4137
4169
  if (settled) return;
4138
4170
  settled = true;
4139
4171
  if (entry.timeoutId) clearTimeout(entry.timeoutId);
4172
+ if (entry.reminderId) clearInterval(entry.reminderId);
4140
4173
  reject2(error);
4141
4174
  };
4142
4175
  if (effectiveTimeout > 0) {
@@ -4148,6 +4181,13 @@ var init_delegate = __esm({
4148
4181
  entry.reject(new Error(`Delegation queue timeout: waited ${effectiveTimeout}ms for an available slot`));
4149
4182
  }, effectiveTimeout);
4150
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
+ }
4151
4191
  this.waitQueue.push(entry);
4152
4192
  });
4153
4193
  }
@@ -4186,18 +4226,14 @@ var init_delegate = __esm({
4186
4226
  const sessionData = this.sessionDelegations.get(parentSessionId);
4187
4227
  const sessionCount = sessionData?.count || 0;
4188
4228
  if (sessionCount >= this.maxPerSession) {
4189
- if (debug) {
4190
- console.error(`[DelegationManager] Session limit (${this.maxPerSession}) reached for queued item, rejecting`);
4191
- }
4229
+ console.error(`[DelegationManager] Session limit (${this.maxPerSession}) reached for queued item, rejecting`);
4192
4230
  toReject.push({ reject: reject2, error: new Error(`Maximum delegations per session (${this.maxPerSession}) reached for session ${parentSessionId}`) });
4193
4231
  continue;
4194
4232
  }
4195
4233
  }
4196
4234
  this._incrementCounters(parentSessionId);
4197
- if (debug) {
4198
- const waitTime = Date.now() - queuedAt;
4199
- console.error(`[DelegationManager] Granted slot from queue (waited ${waitTime}ms). Active: ${this.globalActive}/${this.maxConcurrent}`);
4200
- }
4235
+ const waitTime = Date.now() - queuedAt;
4236
+ console.error(`[DelegationManager] Granted slot from queue (waited ${waitTime}ms). Active: ${this.globalActive}/${this.maxConcurrent}`);
4201
4237
  toResolve.push(resolve9);
4202
4238
  }
4203
4239
  if (toResolve.length > 0 || toReject.length > 0) {
@@ -4247,6 +4283,9 @@ var init_delegate = __esm({
4247
4283
  if (entry.timeoutId) {
4248
4284
  clearTimeout(entry.timeoutId);
4249
4285
  }
4286
+ if (entry.reminderId) {
4287
+ clearInterval(entry.reminderId);
4288
+ }
4250
4289
  if (entry.reject) {
4251
4290
  entry.reject(new Error("DelegationManager was cleaned up"));
4252
4291
  }
@@ -4369,8 +4408,9 @@ Instructions:
4369
4408
  promptType: "code-researcher",
4370
4409
  allowedTools: ["extract"],
4371
4410
  maxIterations: 5,
4372
- delegationManager: options.delegationManager
4411
+ delegationManager: options.delegationManager,
4373
4412
  // Per-instance delegation limits
4413
+ parentAbortSignal: options.parentAbortSignal || null
4374
4414
  // timeout removed - inherit default from delegate (300s)
4375
4415
  });
4376
4416
  return { chunk, result };
@@ -4390,16 +4430,12 @@ async function processChunksParallel(chunks, extractionPrompt, maxWorkers, optio
4390
4430
  return result;
4391
4431
  });
4392
4432
  active.add(promise);
4393
- if (options.debug) {
4394
- console.error(`[analyze_all] Started processing chunk ${chunk.id}/${chunk.total}`);
4395
- }
4433
+ console.error(`[analyze_all] Started processing chunk ${chunk.id}/${chunk.total}`);
4396
4434
  }
4397
4435
  if (active.size > 0) {
4398
4436
  const result = await Promise.race(active);
4399
4437
  results.push(result);
4400
- if (options.debug) {
4401
- console.error(`[analyze_all] Completed chunk ${result.chunk.id}/${result.chunk.total}`);
4402
- }
4438
+ console.error(`[analyze_all] Completed chunk ${result.chunk.id}/${result.chunk.total}`);
4403
4439
  }
4404
4440
  }
4405
4441
  results.sort((a, b) => a.chunk.id - b.chunk.id);
@@ -4469,8 +4505,9 @@ Organize all findings into clear categories with items listed under each.${compl
4469
4505
  promptType: "code-researcher",
4470
4506
  allowedTools: [],
4471
4507
  maxIterations: 5,
4472
- delegationManager: options.delegationManager
4508
+ delegationManager: options.delegationManager,
4473
4509
  // Per-instance delegation limits
4510
+ parentAbortSignal: options.parentAbortSignal || null
4474
4511
  // timeout removed - inherit default from delegate (300s)
4475
4512
  });
4476
4513
  return result;
@@ -4534,8 +4571,9 @@ CRITICAL: Do NOT guess keywords. Actually run searches and see what returns resu
4534
4571
  promptType: "code-researcher",
4535
4572
  // Full tool access for exploration and experimentation
4536
4573
  maxIterations: 15,
4537
- delegationManager: options.delegationManager
4574
+ delegationManager: options.delegationManager,
4538
4575
  // Per-instance delegation limits
4576
+ parentAbortSignal: options.parentAbortSignal || null
4539
4577
  // timeout removed - inherit default from delegate (300s)
4540
4578
  });
4541
4579
  const plan = parsePlanningResult(stripResultTags(result));
@@ -4592,8 +4630,9 @@ When done, use the attempt_completion tool with your answer as the result.`;
4592
4630
  promptType: "code-researcher",
4593
4631
  allowedTools: [],
4594
4632
  maxIterations: 5,
4595
- delegationManager: options.delegationManager
4633
+ delegationManager: options.delegationManager,
4596
4634
  // Per-instance delegation limits
4635
+ parentAbortSignal: options.parentAbortSignal || null
4597
4636
  // timeout removed - inherit default from delegate (300s)
4598
4637
  });
4599
4638
  return stripResultTags(result);
@@ -8890,13 +8929,13 @@ function resolveTargetPath(target, cwd) {
8890
8929
  }
8891
8930
  return filePart + suffix;
8892
8931
  }
8893
- 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;
8894
8933
  var init_common = __esm({
8895
8934
  "src/tools/common.js"() {
8896
8935
  "use strict";
8897
8936
  init_zod();
8898
8937
  searchSchema = external_exports.object({
8899
- query: external_exports.string().describe("Search query with Elasticsearch syntax. Use quotes for exact matches, AND/OR for boolean logic, - for negation."),
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."),
8900
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.'),
8901
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.'),
8902
8941
  maxTokens: external_exports.number().nullable().optional().describe("Maximum tokens to return. Default is 20000. Set to null for unlimited results."),
@@ -8904,7 +8943,7 @@ var init_common = __esm({
8904
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.")
8905
8944
  });
8906
8945
  searchAllSchema = external_exports.object({
8907
- query: external_exports.string().describe("Search query with Elasticsearch syntax. Use quotes for exact matches, AND/OR for boolean logic, - for negation."),
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."),
8908
8947
  path: external_exports.string().optional().default(".").describe("Path to search in."),
8909
8948
  exact: external_exports.boolean().optional().default(false).describe("Use exact matching instead of stemming."),
8910
8949
  maxTokensPerPage: external_exports.number().optional().default(2e4).describe("Tokens per page when paginating. Default 20000."),
@@ -8959,7 +8998,8 @@ var init_common = __esm({
8959
8998
  clearOutputBuffer: external_exports.boolean().optional().default(true).describe("Clear the output buffer from previous execute_plan calls"),
8960
8999
  clearSessionStore: external_exports.boolean().optional().default(false).describe("Clear the session store (persisted data across execute_plan calls)")
8961
9000
  });
8962
- 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.";
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.';
8963
9003
  queryDescription = "Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.";
8964
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.";
8965
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.";
@@ -9144,11 +9184,41 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
9144
9184
  "- extract: Verify code snippets to ensure targets are actually relevant before including them.",
9145
9185
  "- listFiles: Understand directory structure to find where relevant code might live.",
9146
9186
  "",
9147
- "Strategy for complex queries:",
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:",
9148
9217
  "1. Analyze the query - identify key concepts, entities, and relationships",
9149
- '2. Run focused searches for each independent concept (e.g., for "how do payments work and how are emails sent", search "payments" and "emails" separately since they are unrelated)',
9150
- "3. Use extract to verify relevance of promising results",
9151
- "4. Combine all relevant targets in your final response",
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",
9152
9222
  "",
9153
9223
  `Query: ${searchQuery}`,
9154
9224
  `Search path(s): ${searchPath}`,
@@ -9199,9 +9269,12 @@ var init_vercel = __esm({
9199
9269
  }
9200
9270
  return result;
9201
9271
  };
9272
+ const previousSearches = /* @__PURE__ */ new Set();
9273
+ const paginationCounts = /* @__PURE__ */ new Map();
9274
+ const MAX_PAGES_PER_QUERY = 3;
9202
9275
  return tool({
9203
9276
  name: "search",
9204
- description: searchDelegate ? `${searchDescription} (delegates code search to a subagent and returns extracted code blocks)` : searchDescription,
9277
+ description: searchDelegate ? searchDelegateDescription : searchDescription,
9205
9278
  inputSchema: searchSchema,
9206
9279
  execute: async ({ query: searchQuery, path: path9, allow_tests, exact, maxTokens: paramMaxTokens, language, session, nextPage }) => {
9207
9280
  const effectiveMaxTokens = paramMaxTokens || maxTokens;
@@ -9239,6 +9312,26 @@ var init_vercel = __esm({
9239
9312
  return await search(searchOptions);
9240
9313
  };
9241
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
+ }
9242
9335
  try {
9243
9336
  const result = maybeAnnotate(await runRawSearch());
9244
9337
  if (options.fileTracker && typeof result === "string") {
@@ -9277,7 +9370,8 @@ var init_vercel = __esm({
9277
9370
  promptType: "code-searcher",
9278
9371
  allowedTools: ["search", "extract", "listFiles", "attempt_completion"],
9279
9372
  searchDelegate: false,
9280
- schema: CODE_SEARCH_SCHEMA
9373
+ schema: CODE_SEARCH_SCHEMA,
9374
+ parentAbortSignal: options.parentAbortSignal || null
9281
9375
  });
9282
9376
  const delegateResult = options.tracer?.withSpan ? await options.tracer.withSpan("search.delegate", runDelegation, {
9283
9377
  "search.query": searchQuery,
@@ -9491,7 +9585,7 @@ var init_vercel = __esm({
9491
9585
  name: "delegate",
9492
9586
  description: delegateDescription,
9493
9587
  inputSchema: delegateSchema,
9494
- execute: async ({ task, currentIteration, maxIterations, parentSessionId, path: path9, provider, model, tracer, searchDelegate }) => {
9588
+ execute: async ({ task, currentIteration, maxIterations, parentSessionId, path: path9, provider, model, tracer, searchDelegate, parentAbortSignal }) => {
9495
9589
  if (!task || typeof task !== "string") {
9496
9590
  throw new Error("Task parameter is required and must be a non-empty string");
9497
9591
  }
@@ -9549,8 +9643,9 @@ var init_vercel = __esm({
9549
9643
  enableMcp,
9550
9644
  mcpConfig,
9551
9645
  mcpConfigPath,
9552
- delegationManager
9646
+ delegationManager,
9553
9647
  // Per-instance delegation limits
9648
+ parentAbortSignal
9554
9649
  });
9555
9650
  return result;
9556
9651
  }
@@ -9588,8 +9683,9 @@ var init_vercel = __esm({
9588
9683
  provider: options.provider,
9589
9684
  model: options.model,
9590
9685
  tracer: options.tracer,
9591
- delegationManager
9686
+ delegationManager,
9592
9687
  // Per-instance delegation limits
9688
+ parentAbortSignal: options.parentAbortSignal || null
9593
9689
  });
9594
9690
  return result;
9595
9691
  } catch (error) {
@@ -22106,6 +22202,7 @@ function generateSandboxGlobals(options) {
22106
22202
  executing.add(p);
22107
22203
  results.push(p);
22108
22204
  if (executing.size >= mapConcurrency) {
22205
+ console.error(`[map] Concurrency limit reached (${executing.size}/${mapConcurrency}), waiting for a slot...`);
22109
22206
  await Promise.race(executing);
22110
22207
  }
22111
22208
  }
@@ -81768,7 +81865,9 @@ __export(ProbeAgent_exports, {
81768
81865
  ENGINE_ACTIVITY_TIMEOUT_DEFAULT: () => ENGINE_ACTIVITY_TIMEOUT_DEFAULT,
81769
81866
  ENGINE_ACTIVITY_TIMEOUT_MAX: () => ENGINE_ACTIVITY_TIMEOUT_MAX,
81770
81867
  ENGINE_ACTIVITY_TIMEOUT_MIN: () => ENGINE_ACTIVITY_TIMEOUT_MIN,
81771
- ProbeAgent: () => ProbeAgent
81868
+ ProbeAgent: () => ProbeAgent,
81869
+ debugLogToolResults: () => debugLogToolResults,
81870
+ debugTruncate: () => debugTruncate
81772
81871
  });
81773
81872
  import dotenv2 from "dotenv";
81774
81873
  import { createAnthropic as createAnthropic2 } from "@ai-sdk/anthropic";
@@ -81781,6 +81880,19 @@ import { EventEmitter as EventEmitter5 } from "events";
81781
81880
  import { existsSync as existsSync7 } from "fs";
81782
81881
  import { readFile as readFile3, stat, readdir as readdir3 } from "fs/promises";
81783
81882
  import { resolve as resolve7, isAbsolute as isAbsolute6, dirname as dirname5, basename, normalize as normalize2, sep as sep5 } from "path";
81883
+ function debugTruncate(s, limit = 200) {
81884
+ if (s.length <= limit) return s;
81885
+ const half = Math.floor(limit / 2);
81886
+ return s.substring(0, half) + ` ... [${s.length} chars] ... ` + s.substring(s.length - half);
81887
+ }
81888
+ function debugLogToolResults(toolResults) {
81889
+ if (!toolResults || toolResults.length === 0) return;
81890
+ for (const tr of toolResults) {
81891
+ const argsStr = JSON.stringify(tr.args || {});
81892
+ const resultStr = typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result || "");
81893
+ console.log(`[DEBUG] tool: ${tr.toolName} | args: ${debugTruncate(argsStr)} | result: ${debugTruncate(resultStr)}`);
81894
+ }
81895
+ }
81784
81896
  var ENGINE_ACTIVITY_TIMEOUT_DEFAULT, ENGINE_ACTIVITY_TIMEOUT_MIN, ENGINE_ACTIVITY_TIMEOUT_MAX, MAX_TOOL_ITERATIONS, MAX_HISTORY_MESSAGES, MAX_IMAGE_FILE_SIZE, ProbeAgent;
81785
81897
  var init_ProbeAgent = __esm({
81786
81898
  "src/agent/ProbeAgent.js"() {
@@ -81889,6 +82001,7 @@ var init_ProbeAgent = __esm({
81889
82001
  this.enableExecutePlan = !!options.enableExecutePlan;
81890
82002
  this.debug = options.debug || process.env.DEBUG === "1";
81891
82003
  this.cancelled = false;
82004
+ this._abortController = new AbortController();
81892
82005
  this.tracer = options.tracer || null;
81893
82006
  this.outline = !!options.outline;
81894
82007
  this.searchDelegate = options.searchDelegate !== void 0 ? !!options.searchDelegate : true;
@@ -81917,6 +82030,7 @@ var init_ProbeAgent = __esm({
81917
82030
  this.completionPrompt = options.completionPrompt || null;
81918
82031
  this.thinkingEffort = options.thinkingEffort || null;
81919
82032
  const effectiveAllowedTools = options.disableTools ? [] : options.allowedTools;
82033
+ this._rawAllowedTools = options.allowedTools;
81920
82034
  this.allowedTools = this._parseAllowedTools(effectiveAllowedTools);
81921
82035
  this.storageAdapter = options.storageAdapter || new InMemoryStorageAdapter();
81922
82036
  this.hooks = new HookManager();
@@ -82059,6 +82173,16 @@ var init_ProbeAgent = __esm({
82059
82173
  _filterMcpTools(mcpToolNames) {
82060
82174
  return mcpToolNames.filter((toolName) => this._isMcpToolAllowed(toolName));
82061
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
+ }
82062
82186
  /**
82063
82187
  * Check if tracer is AppTracer (expects sessionId as first param) vs SimpleAppTracer
82064
82188
  * @returns {boolean} - True if tracer is AppTracer style (requires sessionId)
@@ -82334,6 +82458,8 @@ var init_ProbeAgent = __esm({
82334
82458
  searchDelegateModel: this.searchDelegateModel,
82335
82459
  delegationManager: this.delegationManager,
82336
82460
  // Per-instance delegation limits
82461
+ parentAbortSignal: this._abortController.signal,
82462
+ // Propagate cancellation to delegations
82337
82463
  outputBuffer: this._outputBuffer,
82338
82464
  concurrencyLimiter: this.concurrencyLimiter,
82339
82465
  // Global AI concurrency limiter
@@ -82350,7 +82476,7 @@ var init_ProbeAgent = __esm({
82350
82476
  if (wrappedTools.searchToolInstance && isToolAllowed("search")) {
82351
82477
  this.toolImplementations.search = wrappedTools.searchToolInstance;
82352
82478
  }
82353
- if (wrappedTools.queryToolInstance && isToolAllowed("query")) {
82479
+ if (wrappedTools.queryToolInstance && isToolAllowed("query") && this._isQueryExplicitlyAllowed()) {
82354
82480
  this.toolImplementations.query = wrappedTools.queryToolInstance;
82355
82481
  }
82356
82482
  if (wrappedTools.extractToolInstance && isToolAllowed("extract")) {
@@ -82781,6 +82907,15 @@ var init_ProbeAgent = __esm({
82781
82907
  }
82782
82908
  const controller = new AbortController();
82783
82909
  const timeoutState = { timeoutId: null };
82910
+ if (this._abortController.signal.aborted) {
82911
+ controller.abort();
82912
+ } else {
82913
+ const onAgentAbort = () => controller.abort();
82914
+ this._abortController.signal.addEventListener("abort", onAgentAbort, { once: true });
82915
+ controller.signal.addEventListener("abort", () => {
82916
+ this._abortController.signal.removeEventListener("abort", onAgentAbort);
82917
+ }, { once: true });
82918
+ }
82784
82919
  if (this.maxOperationTimeout && this.maxOperationTimeout > 0) {
82785
82920
  timeoutState.timeoutId = setTimeout(() => {
82786
82921
  controller.abort();
@@ -83084,7 +83219,8 @@ var init_ProbeAgent = __esm({
83084
83219
  allowEdit: this.allowEdit,
83085
83220
  allowedTools: allowedToolsForDelegate,
83086
83221
  debug: this.debug,
83087
- tracer: this.tracer
83222
+ tracer: this.tracer,
83223
+ parentAbortSignal: this._abortController.signal
83088
83224
  };
83089
83225
  if (this.debug) {
83090
83226
  console.log(`[DEBUG] Executing delegate tool`);
@@ -83279,12 +83415,13 @@ var init_ProbeAgent = __esm({
83279
83415
  const toolMap = {
83280
83416
  search: {
83281
83417
  schema: searchSchema,
83282
- description: "Search code in the repository using keyword queries with Elasticsearch syntax."
83283
- },
83284
- query: {
83285
- schema: querySchema,
83286
- 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."
83287
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
+ // },
83288
83425
  extract: {
83289
83426
  schema: extractSchema,
83290
83427
  description: "Extract code blocks from files based on file paths and optional line numbers."
@@ -83952,25 +84089,27 @@ ${this.architectureContext.content}
83952
84089
  } else {
83953
84090
  systemPrompt += predefinedPrompts["code-explorer"] + "\n\n";
83954
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.";
83955
84093
  systemPrompt += `You have access to powerful code search and analysis tools through MCP:
83956
- - search: Find code patterns using semantic search
84094
+ ${searchToolDesc1}
83957
84095
  - extract: Extract specific code sections with context
83958
- - query: Use AST patterns for structural code matching
83959
84096
  - listFiles: Browse directory contents
83960
84097
  - searchFiles: Find files by name patterns`;
83961
84098
  if (this.enableBash) {
83962
84099
  systemPrompt += `
83963
84100
  - bash: Execute bash commands for system operations`;
83964
84101
  }
83965
- const searchGuidance = this.searchDelegate ? "1. Start with search to retrieve extracted code blocks" : "1. Start with search to find relevant code patterns";
83966
- const extractGuidance = this.searchDelegate ? "2. Use extract only if you need more context or a full file" : "2. Use extract to get detailed context when needed";
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";
83967
84104
  systemPrompt += `
83968
84105
 
83969
84106
  When exploring code:
83970
- ${searchGuidance}
83971
- ${extractGuidance}
84107
+ ${searchGuidance1}
84108
+ ${extractGuidance1}
83972
84109
  3. Prefer focused, specific searches over broad queries
83973
- 4. Combine multiple tools to build complete understanding`;
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`;
83974
84113
  if (this.allowedFolders && this.allowedFolders.length > 0) {
83975
84114
  systemPrompt += `
83976
84115
 
@@ -84005,25 +84144,27 @@ Workspace: ${this.allowedFolders.join(", ")}`;
84005
84144
  } else {
84006
84145
  systemPrompt += predefinedPrompts["code-explorer"] + "\n\n";
84007
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.";
84008
84148
  systemPrompt += `You have access to powerful code search and analysis tools through MCP:
84009
- - search: Find code patterns using semantic search
84149
+ ${searchToolDesc2}
84010
84150
  - extract: Extract specific code sections with context
84011
- - query: Use AST patterns for structural code matching
84012
84151
  - listFiles: Browse directory contents
84013
84152
  - searchFiles: Find files by name patterns`;
84014
84153
  if (this.enableBash) {
84015
84154
  systemPrompt += `
84016
84155
  - bash: Execute bash commands for system operations`;
84017
84156
  }
84018
- const searchGuidance = this.searchDelegate ? "1. Start with search to retrieve extracted code blocks" : "1. Start with search to find relevant code patterns";
84019
- const extractGuidance = this.searchDelegate ? "2. Use extract only if you need more context or a full file" : "2. Use extract to get detailed context when needed";
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";
84020
84159
  systemPrompt += `
84021
84160
 
84022
84161
  When exploring code:
84023
- ${searchGuidance}
84024
- ${extractGuidance}
84162
+ ${searchGuidance2}
84163
+ ${extractGuidance2}
84025
84164
  3. Prefer focused, specific searches over broad queries
84026
- 4. Combine multiple tools to build complete understanding`;
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`;
84027
84168
  if (this.allowedFolders && this.allowedFolders.length > 0) {
84028
84169
  systemPrompt += `
84029
84170
 
@@ -84074,10 +84215,10 @@ Workspace: ${this.allowedFolders.join(", ")}`;
84074
84215
  Follow these instructions carefully:
84075
84216
  1. Analyze the user's request.
84076
84217
  2. Use the available tools step-by-step to fulfill the request.
84077
- 3. You should always prefer the search tool for code-related questions.${this.searchDelegate ? " It already returns extracted code blocks; use extract only to expand context or read full files." : " Read full files only if really necessary."}
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."}
84078
84219
  4. Ensure to get really deep and understand the full picture before answering.
84079
84220
  5. Once the task is fully completed, use the attempt_completion tool to provide the final result.
84080
- 6. Prefer concise and focused search queries. Use specific keywords and phrases to narrow down results.${this.allowEdit ? `
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 ? `
84081
84222
  7. When modifying files, choose the appropriate tool:
84082
84223
  - Use 'edit' for all code modifications:
84083
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.
@@ -84401,6 +84542,10 @@ You are working with a workspace. Available paths: ${workspaceDesc}
84401
84542
  completionResult = result;
84402
84543
  completionAttempted = true;
84403
84544
  }, toolContext);
84545
+ if (this.debug) {
84546
+ const toolNames = Object.keys(tools2);
84547
+ console.log(`[DEBUG] Agent tools registered (${toolNames.length}): ${toolNames.join(", ")}`);
84548
+ }
84404
84549
  let maxResponseTokens = this.maxResponseTokens;
84405
84550
  if (!maxResponseTokens) {
84406
84551
  maxResponseTokens = 4e3;
@@ -84440,6 +84585,7 @@ You are working with a workspace. Available paths: ${workspaceDesc}
84440
84585
  }
84441
84586
  if (this.debug) {
84442
84587
  console.log(`[DEBUG] Step ${currentIteration}/${maxIterations} finished (reason: ${finishReason}, tools: ${toolResults?.length || 0})`);
84588
+ debugLogToolResults(toolResults);
84443
84589
  }
84444
84590
  }
84445
84591
  };
@@ -84596,6 +84742,7 @@ Double-check your response based on the criteria above. If everything looks good
84596
84742
  }
84597
84743
  if (this.debug) {
84598
84744
  console.log(`[DEBUG] Completion prompt step finished (reason: ${finishReason}, tools: ${toolResults?.length || 0})`);
84745
+ debugLogToolResults(toolResults);
84599
84746
  }
84600
84747
  }
84601
84748
  };
@@ -85306,6 +85453,9 @@ Convert your previous response content into actual JSON data that follows this s
85306
85453
  * Clean up resources (including MCP connections)
85307
85454
  */
85308
85455
  async cleanup() {
85456
+ if (!this._abortController.signal.aborted) {
85457
+ this._abortController.abort();
85458
+ }
85309
85459
  if (this.mcpBridge) {
85310
85460
  try {
85311
85461
  await this.mcpBridge.cleanup();
@@ -85329,14 +85479,25 @@ Convert your previous response content into actual JSON data that follows this s
85329
85479
  this.clearHistory();
85330
85480
  }
85331
85481
  /**
85332
- * Cancel the current request
85482
+ * Cancel the current request and all in-flight delegations.
85483
+ * Aborts the internal AbortController so streamText, subagents,
85484
+ * and any code checking the signal will stop.
85333
85485
  */
85334
85486
  cancel() {
85335
85487
  this.cancelled = true;
85488
+ this._abortController.abort();
85336
85489
  if (this.debug) {
85337
85490
  console.log(`[DEBUG] Agent cancelled for session ${this.sessionId}`);
85338
85491
  }
85339
85492
  }
85493
+ /**
85494
+ * Get the abort signal for this agent.
85495
+ * Delegations and subagents should check this signal.
85496
+ * @returns {AbortSignal}
85497
+ */
85498
+ get abortSignal() {
85499
+ return this._abortController.signal;
85500
+ }
85340
85501
  };
85341
85502
  }
85342
85503
  });