@probelabs/visor 0.1.165-ee → 0.1.166-ee

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.
Files changed (52) hide show
  1. package/dist/index.js +525 -120
  2. package/dist/pr-analyzer.d.ts +2 -1
  3. package/dist/pr-analyzer.d.ts.map +1 -1
  4. package/dist/providers/http-client-provider.d.ts.map +1 -1
  5. package/dist/sdk/{check-provider-registry-5ZE2KMA2.mjs → check-provider-registry-PU67PWTU.mjs} +5 -5
  6. package/dist/sdk/{check-provider-registry-TTVN3V2O.mjs → check-provider-registry-TGPICTHD.mjs} +5 -5
  7. package/dist/sdk/{chunk-XDIBL7QB.mjs → chunk-E7NRUDWL.mjs} +2 -2
  8. package/dist/sdk/{chunk-ODEDLFSQ.mjs → chunk-P5P6BOO7.mjs} +160 -20
  9. package/dist/sdk/chunk-P5P6BOO7.mjs.map +1 -0
  10. package/dist/sdk/{chunk-J236ZVYX.mjs → chunk-RV5SK4FZ.mjs} +3 -3
  11. package/dist/sdk/{chunk-S47KBQQK.mjs → chunk-T5USZCCM.mjs} +2 -2
  12. package/dist/sdk/{chunk-S47KBQQK.mjs.map → chunk-T5USZCCM.mjs.map} +1 -1
  13. package/dist/sdk/{chunk-GOJRNYTV.mjs → chunk-WSYVK6ML.mjs} +188 -22
  14. package/dist/sdk/chunk-WSYVK6ML.mjs.map +1 -0
  15. package/dist/sdk/{failure-condition-evaluator-N3VNLWZD.mjs → failure-condition-evaluator-GPANOHP2.mjs} +3 -3
  16. package/dist/sdk/{github-frontend-ATORHHF6.mjs → github-frontend-P274ISBJ.mjs} +3 -3
  17. package/dist/sdk/{host-OBXKDFT7.mjs → host-AIMRV5YL.mjs} +2 -2
  18. package/dist/sdk/{host-QFABFVSJ.mjs → host-QYPOS4R6.mjs} +2 -2
  19. package/dist/sdk/{routing-TGJD66Q5.mjs → routing-BXHP2E62.mjs} +4 -4
  20. package/dist/sdk/{schedule-tool-D5TSTGP2.mjs → schedule-tool-5FVFYH2A.mjs} +5 -5
  21. package/dist/sdk/{schedule-tool-SBBVNRBS.mjs → schedule-tool-MQHISNJ6.mjs} +5 -5
  22. package/dist/sdk/{schedule-tool-handler-DKHHPZAG.mjs → schedule-tool-handler-4TCT2P7A.mjs} +5 -5
  23. package/dist/sdk/{schedule-tool-handler-DPZEXA25.mjs → schedule-tool-handler-TZYXM664.mjs} +5 -5
  24. package/dist/sdk/sdk.js +146 -6
  25. package/dist/sdk/sdk.js.map +1 -1
  26. package/dist/sdk/sdk.mjs +4 -4
  27. package/dist/sdk/{trace-helpers-J5CJ4PUN.mjs → trace-helpers-UG6FOWVV.mjs} +2 -2
  28. package/dist/sdk/{workflow-check-provider-HGHSY5QF.mjs → workflow-check-provider-BE2SVYWW.mjs} +5 -5
  29. package/dist/sdk/{workflow-check-provider-T6WFK4RB.mjs → workflow-check-provider-QKHL6AFT.mjs} +5 -5
  30. package/dist/slack/socket-runner.d.ts +14 -0
  31. package/dist/slack/socket-runner.d.ts.map +1 -1
  32. package/dist/utils/oauth2-token-cache.d.ts +44 -0
  33. package/dist/utils/oauth2-token-cache.d.ts.map +1 -0
  34. package/package.json +2 -2
  35. package/dist/sdk/chunk-GOJRNYTV.mjs.map +0 -1
  36. package/dist/sdk/chunk-ODEDLFSQ.mjs.map +0 -1
  37. /package/dist/sdk/{check-provider-registry-5ZE2KMA2.mjs.map → check-provider-registry-PU67PWTU.mjs.map} +0 -0
  38. /package/dist/sdk/{check-provider-registry-TTVN3V2O.mjs.map → check-provider-registry-TGPICTHD.mjs.map} +0 -0
  39. /package/dist/sdk/{chunk-XDIBL7QB.mjs.map → chunk-E7NRUDWL.mjs.map} +0 -0
  40. /package/dist/sdk/{chunk-J236ZVYX.mjs.map → chunk-RV5SK4FZ.mjs.map} +0 -0
  41. /package/dist/sdk/{failure-condition-evaluator-N3VNLWZD.mjs.map → failure-condition-evaluator-GPANOHP2.mjs.map} +0 -0
  42. /package/dist/sdk/{github-frontend-ATORHHF6.mjs.map → github-frontend-P274ISBJ.mjs.map} +0 -0
  43. /package/dist/sdk/{host-OBXKDFT7.mjs.map → host-AIMRV5YL.mjs.map} +0 -0
  44. /package/dist/sdk/{host-QFABFVSJ.mjs.map → host-QYPOS4R6.mjs.map} +0 -0
  45. /package/dist/sdk/{routing-TGJD66Q5.mjs.map → routing-BXHP2E62.mjs.map} +0 -0
  46. /package/dist/sdk/{schedule-tool-D5TSTGP2.mjs.map → schedule-tool-5FVFYH2A.mjs.map} +0 -0
  47. /package/dist/sdk/{schedule-tool-SBBVNRBS.mjs.map → schedule-tool-MQHISNJ6.mjs.map} +0 -0
  48. /package/dist/sdk/{schedule-tool-handler-DKHHPZAG.mjs.map → schedule-tool-handler-4TCT2P7A.mjs.map} +0 -0
  49. /package/dist/sdk/{schedule-tool-handler-DPZEXA25.mjs.map → schedule-tool-handler-TZYXM664.mjs.map} +0 -0
  50. /package/dist/sdk/{trace-helpers-J5CJ4PUN.mjs.map → trace-helpers-UG6FOWVV.mjs.map} +0 -0
  51. /package/dist/sdk/{workflow-check-provider-HGHSY5QF.mjs.map → workflow-check-provider-BE2SVYWW.mjs.map} +0 -0
  52. /package/dist/sdk/{workflow-check-provider-T6WFK4RB.mjs.map → workflow-check-provider-QKHL6AFT.mjs.map} +0 -0
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- process.env.VISOR_VERSION = '0.1.165';
3
- process.env.PROBE_VERSION = '0.6.0-rc278';
4
- process.env.VISOR_COMMIT_SHA = '0431072b1fb3764b178496aa042aaa2a90adfd84';
5
- process.env.VISOR_COMMIT_SHORT = '0431072';
2
+ process.env.VISOR_VERSION = '0.1.166';
3
+ process.env.PROBE_VERSION = '0.6.0-rc280';
4
+ process.env.VISOR_COMMIT_SHA = 'eff5ed2df07d89e22e1c88d7d96b97572b0fd908';
5
+ process.env.VISOR_COMMIT_SHORT = 'eff5ed2';
6
6
  /******/ (() => { // webpackBootstrap
7
7
  /******/ var __webpack_modules__ = ({
8
8
 
@@ -180418,10 +180418,12 @@ const file_exclusion_1 = __nccwpck_require__(69342);
180418
180418
  class PRAnalyzer {
180419
180419
  octokit;
180420
180420
  maxRetries;
180421
+ baseDelay;
180421
180422
  fileExclusionHelper;
180422
- constructor(octokit, maxRetries = 3, workingDirectory = path.resolve(process.cwd())) {
180423
+ constructor(octokit, maxRetries = 3, workingDirectory = path.resolve(process.cwd()), baseDelay = 1000) {
180423
180424
  this.octokit = octokit;
180424
180425
  this.maxRetries = maxRetries;
180426
+ this.baseDelay = baseDelay;
180425
180427
  this.fileExclusionHelper = new file_exclusion_1.FileExclusionHelper(workingDirectory);
180426
180428
  }
180427
180429
  /**
@@ -180613,7 +180615,7 @@ class PRAnalyzer {
180613
180615
  }
180614
180616
  // Check if this is a retryable error
180615
180617
  if (this.isRetryableError(error)) {
180616
- const delay = Math.min(1000 * Math.pow(2, attempt), 5000); // Exponential backoff, max 5s
180618
+ const delay = Math.min(this.baseDelay * Math.pow(2, attempt), this.baseDelay * 5); // Exponential backoff
180617
180619
  await new Promise(resolve => setTimeout(resolve, delay));
180618
180620
  }
180619
180621
  else {
@@ -187344,6 +187346,7 @@ const liquid_extensions_1 = __nccwpck_require__(33042);
187344
187346
  const env_resolver_1 = __nccwpck_require__(58749);
187345
187347
  const sandbox_1 = __nccwpck_require__(12630);
187346
187348
  const template_context_1 = __nccwpck_require__(1581);
187349
+ const oauth2_token_cache_1 = __nccwpck_require__(34713);
187347
187350
  const logger_1 = __nccwpck_require__(86999);
187348
187351
  const fs = __importStar(__nccwpck_require__(79896));
187349
187352
  const path = __importStar(__nccwpck_require__(16928));
@@ -187375,13 +187378,15 @@ class HttpClientProvider extends check_provider_interface_1.CheckProvider {
187375
187378
  if (cfg.type !== 'http_client') {
187376
187379
  return false;
187377
187380
  }
187378
- // Must have URL specified
187379
- if (typeof cfg.url !== 'string' || !cfg.url) {
187381
+ // Must have either `url` or `base_url` specified
187382
+ const hasUrl = typeof cfg.url === 'string' && cfg.url;
187383
+ const hasBaseUrl = typeof cfg.base_url === 'string' && cfg.base_url;
187384
+ if (!hasUrl && !hasBaseUrl) {
187380
187385
  return false;
187381
187386
  }
187382
- // Validate URL format
187387
+ // Validate URL format (check whichever is provided)
187383
187388
  try {
187384
- new URL(cfg.url);
187389
+ new URL((hasUrl ? cfg.url : cfg.base_url));
187385
187390
  return true;
187386
187391
  }
187387
187392
  catch {
@@ -187389,13 +187394,34 @@ class HttpClientProvider extends check_provider_interface_1.CheckProvider {
187389
187394
  }
187390
187395
  }
187391
187396
  async execute(prInfo, config, dependencyResults, context) {
187392
- const url = config.url;
187397
+ const baseUrl = config.base_url;
187398
+ const rawPath = config.path;
187399
+ const pathParams = config.params || {};
187400
+ const queryParams = config.query || {};
187401
+ const authConfig = config.auth;
187402
+ // Build URL: either direct `url` or `base_url` + `path` with param substitution
187403
+ let url;
187404
+ if (baseUrl && rawPath) {
187405
+ // Substitute {param} placeholders in path
187406
+ let resolvedPath = rawPath;
187407
+ for (const [key, value] of Object.entries(pathParams)) {
187408
+ resolvedPath = resolvedPath.replace(`{${key}}`, encodeURIComponent(value));
187409
+ }
187410
+ url = `${baseUrl.replace(/\/+$/, '')}/${resolvedPath.replace(/^\/+/, '')}`;
187411
+ // Append query parameters
187412
+ if (Object.keys(queryParams).length > 0) {
187413
+ const qs = new URLSearchParams(queryParams).toString();
187414
+ url += `${url.includes('?') ? '&' : '?'}${qs}`;
187415
+ }
187416
+ }
187417
+ else {
187418
+ url = config.url;
187419
+ }
187393
187420
  const method = config.method || 'GET';
187394
187421
  const headers = config.headers || {};
187395
187422
  const timeout = config.timeout || 30000;
187396
187423
  const transform = config.transform;
187397
187424
  const transformJs = config.transform_js;
187398
- const bodyTemplate = config.body;
187399
187425
  const outputFileTemplate = config.output_file;
187400
187426
  const skipIfExists = config.skip_if_exists !== false; // Default true for caching
187401
187427
  // Track resolved URL for error messages
@@ -187416,9 +187442,14 @@ class HttpClientProvider extends check_provider_interface_1.CheckProvider {
187416
187442
  renderedUrl = await this.liquid.parseAndRender(renderedUrl, templateContext);
187417
187443
  resolvedUrlForErrors = renderedUrl; // Update after Liquid rendering
187418
187444
  }
187419
- // Prepare request body if provided
187445
+ // Prepare request body supports both Liquid template strings and JSON objects
187420
187446
  let requestBody;
187421
- if (bodyTemplate) {
187447
+ const rawBody = config.body;
187448
+ const bodyTemplate = typeof rawBody === 'string' ? rawBody : undefined;
187449
+ if (rawBody && typeof rawBody === 'object') {
187450
+ requestBody = JSON.stringify(rawBody);
187451
+ }
187452
+ else if (bodyTemplate) {
187422
187453
  // First resolve shell-style environment variables
187423
187454
  let resolvedBody = String(env_resolver_1.EnvironmentResolver.resolveValue(bodyTemplate));
187424
187455
  // Then render Liquid templates if present
@@ -187444,6 +187475,12 @@ class HttpClientProvider extends check_provider_interface_1.CheckProvider {
187444
187475
  logger_1.logger.verbose(`[http_client] ${key}: ${maskedValue}`);
187445
187476
  }
187446
187477
  }
187478
+ // Inject OAuth2 Bearer token if auth config is provided
187479
+ if (authConfig?.type === 'oauth2_client_credentials') {
187480
+ const tokenCache = oauth2_token_cache_1.OAuth2TokenCache.getInstance();
187481
+ const token = await tokenCache.getToken(authConfig);
187482
+ resolvedHeaders['Authorization'] = `Bearer ${token}`;
187483
+ }
187447
187484
  // Resolve output_file path if specified
187448
187485
  let resolvedOutputFile;
187449
187486
  if (outputFileTemplate) {
@@ -187741,6 +187778,11 @@ class HttpClientProvider extends check_provider_interface_1.CheckProvider {
187741
187778
  return [
187742
187779
  'type',
187743
187780
  'url',
187781
+ 'base_url',
187782
+ 'path',
187783
+ 'params',
187784
+ 'query',
187785
+ 'auth',
187744
187786
  'method',
187745
187787
  'headers',
187746
187788
  'body',
@@ -200422,6 +200464,9 @@ class SlackSocketRunner {
200422
200464
  genericScheduler;
200423
200465
  messageTriggerEvaluator;
200424
200466
  activeThreads = new Set();
200467
+ heartbeatTimer;
200468
+ lastPong = 0;
200469
+ closing = false; // prevent duplicate reconnects
200425
200470
  constructor(engine, cfg, opts) {
200426
200471
  const app = opts.appToken || process.env.SLACK_APP_TOKEN || '';
200427
200472
  if (!app)
@@ -200612,21 +200657,93 @@ class SlackSocketRunner {
200612
200657
  return json.url;
200613
200658
  }
200614
200659
  async connect(url) {
200615
- this.ws = new ws_1.default(url);
200616
- this.ws.on('open', () => {
200660
+ // Close previous WebSocket to prevent ghost event handlers
200661
+ this.closeWebSocket();
200662
+ const ws = new ws_1.default(url);
200663
+ this.ws = ws;
200664
+ this.closing = false;
200665
+ ws.on('open', () => {
200617
200666
  this.retryCount = 0; // Reset on successful connection
200667
+ this.lastPong = Date.now();
200618
200668
  logger_1.logger.info('[SlackSocket] WebSocket connected');
200669
+ this.startHeartbeat();
200619
200670
  });
200620
- this.ws.on('close', (code, reason) => {
200671
+ ws.on('close', (code, reason) => {
200621
200672
  logger_1.logger.warn(`[SlackSocket] WebSocket closed: ${code} ${reason}`);
200622
- setTimeout(() => this.restart().catch(() => { }), 1000);
200673
+ this.stopHeartbeat();
200674
+ // Only reconnect if this is still the active WebSocket
200675
+ if (this.ws === ws && !this.closing) {
200676
+ this.closing = true;
200677
+ setTimeout(() => this.restart(), 1000);
200678
+ }
200623
200679
  });
200624
- this.ws.on('error', err => {
200680
+ ws.on('error', err => {
200625
200681
  logger_1.logger.error(`[SlackSocket] WebSocket error: ${err}`);
200626
200682
  });
200627
- this.ws.on('message', data => this.handleMessage(data.toString()).catch(() => { }));
200683
+ ws.on('pong', () => {
200684
+ this.lastPong = Date.now();
200685
+ });
200686
+ ws.on('message', data => this.handleMessage(data.toString()).catch(() => { }));
200687
+ }
200688
+ /**
200689
+ * Close the current WebSocket connection and stop heartbeat.
200690
+ * Safe to call multiple times.
200691
+ */
200692
+ closeWebSocket() {
200693
+ this.stopHeartbeat();
200694
+ if (this.ws) {
200695
+ const old = this.ws;
200696
+ this.ws = undefined;
200697
+ try {
200698
+ // Remove listeners to prevent ghost close/message handlers
200699
+ old.removeAllListeners();
200700
+ old.close();
200701
+ }
200702
+ catch {
200703
+ // best effort
200704
+ }
200705
+ }
200706
+ }
200707
+ /**
200708
+ * Start periodic WebSocket ping to detect dead connections.
200709
+ * If no pong is received within 60s, force a reconnect.
200710
+ */
200711
+ startHeartbeat() {
200712
+ this.stopHeartbeat();
200713
+ const PING_INTERVAL_MS = 30_000; // ping every 30s
200714
+ const PONG_TIMEOUT_MS = 60_000; // dead if no pong for 60s
200715
+ this.heartbeatTimer = setInterval(() => {
200716
+ if (!this.ws || this.ws.readyState !== ws_1.default.OPEN)
200717
+ return;
200718
+ // Check if last pong is stale
200719
+ const sincePong = Date.now() - this.lastPong;
200720
+ if (this.lastPong > 0 && sincePong > PONG_TIMEOUT_MS) {
200721
+ logger_1.logger.warn(`[SlackSocket] No pong received for ${Math.round(sincePong / 1000)}s, forcing reconnect`);
200722
+ this.closeWebSocket();
200723
+ this.closing = true;
200724
+ this.restart();
200725
+ return;
200726
+ }
200727
+ try {
200728
+ this.ws.ping();
200729
+ }
200730
+ catch {
200731
+ // ping failed — connection is likely dead
200732
+ logger_1.logger.warn('[SlackSocket] Ping failed, forcing reconnect');
200733
+ this.closeWebSocket();
200734
+ this.closing = true;
200735
+ this.restart();
200736
+ }
200737
+ }, PING_INTERVAL_MS);
200738
+ }
200739
+ stopHeartbeat() {
200740
+ if (this.heartbeatTimer) {
200741
+ clearInterval(this.heartbeatTimer);
200742
+ this.heartbeatTimer = undefined;
200743
+ }
200628
200744
  }
200629
200745
  async restart() {
200746
+ this.closing = false;
200630
200747
  try {
200631
200748
  const url = await this.openConnection();
200632
200749
  await this.connect(url);
@@ -200636,7 +200753,7 @@ class SlackSocketRunner {
200636
200753
  // Exponential backoff: 2s, 4s, 8s, 16s, 32s, capped at 60s
200637
200754
  const delay = Math.min(2000 * Math.pow(2, this.retryCount - 1), 60000);
200638
200755
  logger_1.logger.error(`[SlackSocket] Restart failed (attempt ${this.retryCount}), retrying in ${Math.round(delay / 1000)}s: ${e instanceof Error ? e.message : e}`);
200639
- setTimeout(() => this.restart().catch(() => { }), delay);
200756
+ setTimeout(() => this.restart(), delay);
200640
200757
  }
200641
200758
  }
200642
200759
  send(obj) {
@@ -200714,6 +200831,16 @@ class SlackSocketRunner {
200714
200831
  return;
200715
200832
  if (env.envelope_id)
200716
200833
  this.send({ envelope_id: env.envelope_id }); // ack ASAP
200834
+ // Handle Slack disconnect events — proactively reconnect before the connection dies
200835
+ if (env.type === 'disconnect') {
200836
+ const reason = env.reason || 'unknown';
200837
+ logger_1.logger.info(`[SlackSocket] Received disconnect event (reason: ${reason}), reconnecting`);
200838
+ // Slack will close the connection shortly; proactively reconnect now
200839
+ this.closeWebSocket();
200840
+ this.closing = true;
200841
+ this.restart();
200842
+ return;
200843
+ }
200717
200844
  if (env.type !== 'events_api' || !env.payload) {
200718
200845
  if (process.env.VISOR_DEBUG === 'true') {
200719
200846
  logger_1.logger.debug(`[SlackSocket] Dropping non-events payload: type=${String(env.type || '-')}`);
@@ -201417,17 +201544,10 @@ class SlackSocketRunner {
201417
201544
  logger_1.logger.warn(`[SlackSocket] Error stopping generic scheduler: ${e instanceof Error ? e.message : e}`);
201418
201545
  }
201419
201546
  }
201420
- // Close WebSocket connection
201421
- if (this.ws) {
201422
- try {
201423
- this.ws.close();
201424
- this.ws = undefined;
201425
- logger_1.logger.info('[SlackSocket] WebSocket closed');
201426
- }
201427
- catch {
201428
- // Best effort
201429
- }
201430
- }
201547
+ // Close WebSocket connection and stop heartbeat
201548
+ this.closing = true; // prevent reconnect on close
201549
+ this.closeWebSocket();
201550
+ logger_1.logger.info('[SlackSocket] WebSocket closed');
201431
201551
  }
201432
201552
  /**
201433
201553
  * Get the scheduler instance
@@ -223021,6 +223141,130 @@ function emitMermaidFromMarkdown(checkName, markdown, origin) {
223021
223141
  }
223022
223142
 
223023
223143
 
223144
+ /***/ }),
223145
+
223146
+ /***/ 34713:
223147
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
223148
+
223149
+ "use strict";
223150
+
223151
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
223152
+ exports.OAuth2TokenCache = void 0;
223153
+ const logger_1 = __nccwpck_require__(86999);
223154
+ const env_resolver_1 = __nccwpck_require__(58749);
223155
+ /**
223156
+ * Singleton cache for OAuth2 tokens.
223157
+ *
223158
+ * - Keyed by hash(token_url + client_id) to support multiple providers
223159
+ * - Lazy refresh: only fetches when expired or near-expiry
223160
+ * - Deduplicates concurrent requests: if two calls hit with an expired token,
223161
+ * both await the same fetch promise
223162
+ */
223163
+ class OAuth2TokenCache {
223164
+ static instance;
223165
+ cache = new Map();
223166
+ static getInstance() {
223167
+ if (!OAuth2TokenCache.instance) {
223168
+ OAuth2TokenCache.instance = new OAuth2TokenCache();
223169
+ }
223170
+ return OAuth2TokenCache.instance;
223171
+ }
223172
+ /** Visible for testing */
223173
+ static resetInstance() {
223174
+ OAuth2TokenCache.instance = undefined;
223175
+ }
223176
+ /**
223177
+ * Get a valid Bearer token for the given config.
223178
+ * Returns a cached token if still valid, otherwise fetches a new one.
223179
+ */
223180
+ async getToken(config) {
223181
+ const clientId = String(env_resolver_1.EnvironmentResolver.resolveValue(config.client_id));
223182
+ const clientSecret = String(env_resolver_1.EnvironmentResolver.resolveValue(config.client_secret));
223183
+ const tokenUrl = String(env_resolver_1.EnvironmentResolver.resolveValue(config.token_url));
223184
+ const bufferMs = (config.token_ttl_buffer ?? 300) * 1000;
223185
+ const cacheKey = `${tokenUrl}|${clientId}`;
223186
+ const cached = this.cache.get(cacheKey);
223187
+ // Return cached token if still valid (with buffer)
223188
+ if (cached && cached.expires_at - bufferMs > Date.now()) {
223189
+ logger_1.logger.verbose('[oauth2] Using cached token');
223190
+ return cached.access_token;
223191
+ }
223192
+ // If another request is already refreshing, await it
223193
+ if (cached?.refreshPromise) {
223194
+ logger_1.logger.verbose('[oauth2] Awaiting in-flight token refresh');
223195
+ return cached.refreshPromise;
223196
+ }
223197
+ // Fetch a new token
223198
+ const refreshPromise = this.fetchToken(tokenUrl, clientId, clientSecret, config.scopes);
223199
+ // Store the promise so concurrent callers share it
223200
+ if (cached) {
223201
+ cached.refreshPromise = refreshPromise;
223202
+ }
223203
+ else {
223204
+ this.cache.set(cacheKey, {
223205
+ access_token: '',
223206
+ expires_at: 0,
223207
+ refreshPromise,
223208
+ });
223209
+ }
223210
+ try {
223211
+ const token = await refreshPromise;
223212
+ return token;
223213
+ }
223214
+ finally {
223215
+ // Clear the in-flight promise regardless of outcome
223216
+ const entry = this.cache.get(cacheKey);
223217
+ if (entry) {
223218
+ entry.refreshPromise = undefined;
223219
+ }
223220
+ }
223221
+ }
223222
+ async fetchToken(tokenUrl, clientId, clientSecret, scopes) {
223223
+ logger_1.logger.verbose(`[oauth2] Fetching token from ${tokenUrl}`);
223224
+ const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
223225
+ const bodyParams = new URLSearchParams({ grant_type: 'client_credentials' });
223226
+ if (scopes?.length) {
223227
+ bodyParams.set('scope', scopes.join(' '));
223228
+ }
223229
+ const response = await fetch(tokenUrl, {
223230
+ method: 'POST',
223231
+ headers: {
223232
+ 'Content-Type': 'application/x-www-form-urlencoded',
223233
+ Authorization: `Basic ${credentials}`,
223234
+ },
223235
+ body: bodyParams.toString(),
223236
+ });
223237
+ if (!response.ok) {
223238
+ let errorDetail = '';
223239
+ try {
223240
+ errorDetail = await response.text();
223241
+ }
223242
+ catch { }
223243
+ throw new Error(`OAuth2 token request failed: HTTP ${response.status} ${response.statusText}${errorDetail ? ` - ${errorDetail.substring(0, 200)}` : ''}`);
223244
+ }
223245
+ const data = (await response.json());
223246
+ if (!data.access_token) {
223247
+ throw new Error('OAuth2 token response missing access_token');
223248
+ }
223249
+ // Default to 1 hour if expires_in not provided
223250
+ const expiresIn = data.expires_in ?? 3600;
223251
+ const expiresAt = Date.now() + expiresIn * 1000;
223252
+ const cacheKey = `${tokenUrl}|${clientId}`;
223253
+ this.cache.set(cacheKey, {
223254
+ access_token: data.access_token,
223255
+ expires_at: expiresAt,
223256
+ });
223257
+ logger_1.logger.verbose(`[oauth2] Token acquired, expires in ${expiresIn}s`);
223258
+ return data.access_token;
223259
+ }
223260
+ /** Clear all cached tokens (for testing or credential rotation) */
223261
+ clear() {
223262
+ this.cache.clear();
223263
+ }
223264
+ }
223265
+ exports.OAuth2TokenCache = OAuth2TokenCache;
223266
+
223267
+
223024
223268
  /***/ }),
223025
223269
 
223026
223270
  /***/ 12630:
@@ -249925,9 +250169,7 @@ async function acquireFileLock(lockPath, version2) {
249925
250169
  };
249926
250170
  try {
249927
250171
  await import_fs_extra2.default.writeFile(lockPath, JSON.stringify(lockData), { flag: "wx" });
249928
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
249929
- console.log(`Acquired file lock: ${lockPath}`);
249930
- }
250172
+ console.log(`Acquired file lock: ${lockPath}`);
249931
250173
  return true;
249932
250174
  } catch (error2) {
249933
250175
  if (error2.code === "EEXIST") {
@@ -249935,15 +250177,11 @@ async function acquireFileLock(lockPath, version2) {
249935
250177
  const existingLock = JSON.parse(await import_fs_extra2.default.readFile(lockPath, "utf-8"));
249936
250178
  const lockAge = Date.now() - existingLock.timestamp;
249937
250179
  if (lockAge > LOCK_TIMEOUT_MS) {
249938
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
249939
- console.log(`Removing stale lock file (age: ${Math.round(lockAge / 1e3)}s, pid: ${existingLock.pid})`);
249940
- }
250180
+ console.log(`Removing stale lock file (age: ${Math.round(lockAge / 1e3)}s, pid: ${existingLock.pid})`);
249941
250181
  await import_fs_extra2.default.remove(lockPath);
249942
250182
  return false;
249943
250183
  }
249944
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
249945
- console.log(`Download in progress by process ${existingLock.pid}, waiting...`);
249946
- }
250184
+ console.log(`Download in progress by process ${existingLock.pid}, waiting...`);
249947
250185
  return false;
249948
250186
  } catch (readError) {
249949
250187
  if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
@@ -249984,36 +250222,36 @@ async function releaseFileLock(lockPath) {
249984
250222
  }
249985
250223
  async function waitForFileLock(lockPath, binaryPath) {
249986
250224
  const startTime = Date.now();
250225
+ let lastStatusTime = startTime;
250226
+ console.log(`Waiting for file lock to clear: ${lockPath}`);
249987
250227
  while (Date.now() - startTime < MAX_LOCK_WAIT_MS) {
249988
250228
  if (await import_fs_extra2.default.pathExists(binaryPath)) {
249989
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
249990
- console.log(`Binary now available at ${binaryPath}, download completed by another process`);
249991
- }
250229
+ const waitedSeconds = Math.round((Date.now() - startTime) / 1e3);
250230
+ console.log(`Binary now available at ${binaryPath}, download completed by another process (waited ${waitedSeconds}s)`);
249992
250231
  return true;
249993
250232
  }
249994
250233
  const lockExists = await import_fs_extra2.default.pathExists(lockPath);
249995
250234
  if (!lockExists) {
249996
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
249997
- console.log(`Lock file removed but binary not found - download may have failed`);
249998
- }
250235
+ console.log(`Lock file removed but binary not found - download may have failed`);
249999
250236
  return false;
250000
250237
  }
250001
250238
  try {
250002
250239
  const lockData = JSON.parse(await import_fs_extra2.default.readFile(lockPath, "utf-8"));
250003
250240
  const lockAge = Date.now() - lockData.timestamp;
250004
250241
  if (lockAge > LOCK_TIMEOUT_MS) {
250005
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
250006
- console.log(`Lock expired (age: ${Math.round(lockAge / 1e3)}s), will retry download`);
250007
- }
250242
+ console.log(`Lock expired (age: ${Math.round(lockAge / 1e3)}s), will retry download`);
250008
250243
  return false;
250009
250244
  }
250010
250245
  } catch {
250011
250246
  }
250247
+ if (Date.now() - lastStatusTime >= 15e3) {
250248
+ const elapsedSeconds = Math.round((Date.now() - startTime) / 1e3);
250249
+ console.log(`Still waiting for file lock (${elapsedSeconds}s/${MAX_LOCK_WAIT_MS / 1e3}s max)`);
250250
+ lastStatusTime = Date.now();
250251
+ }
250012
250252
  await new Promise((resolve8) => setTimeout(resolve8, LOCK_POLL_INTERVAL_MS));
250013
250253
  }
250014
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
250015
- console.log(`Timeout waiting for file lock`);
250016
- }
250254
+ console.log(`Timeout waiting for file lock after ${MAX_LOCK_WAIT_MS / 1e3}s`);
250017
250255
  return false;
250018
250256
  }
250019
250257
  async function withDownloadLock(version2, downloadFn) {
@@ -250027,9 +250265,7 @@ async function withDownloadLock(version2, downloadFn) {
250027
250265
  }
250028
250266
  downloadLocks.delete(lockKey);
250029
250267
  } else {
250030
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
250031
- console.log(`Download already in progress in this process for version ${lockKey}, waiting...`);
250032
- }
250268
+ console.log(`Download already in progress in this process for version ${lockKey}, waiting...`);
250033
250269
  try {
250034
250270
  return await lock.promise;
250035
250271
  } catch (error2) {
@@ -250039,10 +250275,16 @@ async function withDownloadLock(version2, downloadFn) {
250039
250275
  }
250040
250276
  }
250041
250277
  }
250278
+ let timeoutId = null;
250042
250279
  const downloadPromise = Promise.race([
250043
250280
  downloadFn(),
250044
250281
  new Promise(
250045
- (_, reject2) => setTimeout(() => reject2(new Error(`Download timeout after ${LOCK_TIMEOUT_MS / 1e3}s`)), LOCK_TIMEOUT_MS)
250282
+ (_, reject2) => {
250283
+ timeoutId = setTimeout(() => reject2(new Error(`Download timeout after ${LOCK_TIMEOUT_MS / 1e3}s`)), LOCK_TIMEOUT_MS);
250284
+ if (timeoutId.unref) {
250285
+ timeoutId.unref();
250286
+ }
250287
+ }
250046
250288
  )
250047
250289
  ]);
250048
250290
  downloadLocks.set(lockKey, {
@@ -250053,6 +250295,9 @@ async function withDownloadLock(version2, downloadFn) {
250053
250295
  const result = await downloadPromise;
250054
250296
  return result;
250055
250297
  } finally {
250298
+ if (timeoutId) {
250299
+ clearTimeout(timeoutId);
250300
+ }
250056
250301
  downloadLocks.delete(lockKey);
250057
250302
  }
250058
250303
  }
@@ -285583,14 +285828,14 @@ function resolveTargetPath(target, cwd) {
285583
285828
  }
285584
285829
  return filePart + suffix;
285585
285830
  }
285586
- var import_path5, searchSchema, searchAllSchema, querySchema, extractSchema, delegateSchema, listSkillsSchema, useSkillSchema, listFilesSchema, searchFilesSchema, readImageSchema, bashSchema, analyzeAllSchema, executePlanSchema, cleanupExecutePlanSchema, attemptCompletionSchema, searchDescription, queryDescription, extractDescription, delegateDescription, bashDescription, analyzeAllDescription;
285831
+ var import_path5, searchSchema, searchAllSchema, querySchema, extractSchema, delegateSchema, listSkillsSchema, useSkillSchema, listFilesSchema, searchFilesSchema, readImageSchema, bashSchema, analyzeAllSchema, executePlanSchema, cleanupExecutePlanSchema, attemptCompletionSchema, searchDescription, searchDelegateDescription, queryDescription, extractDescription, delegateDescription, bashDescription, analyzeAllDescription;
285587
285832
  var init_common2 = __esm({
285588
285833
  "src/tools/common.js"() {
285589
285834
  "use strict";
285590
285835
  init_zod();
285591
285836
  import_path5 = __nccwpck_require__(16928);
285592
285837
  searchSchema = external_exports.object({
285593
- query: external_exports.string().describe("Search query with Elasticsearch syntax. Use quotes for exact matches, AND/OR for boolean logic, - for negation."),
285838
+ 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."),
285594
285839
  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.'),
285595
285840
  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.'),
285596
285841
  maxTokens: external_exports.number().nullable().optional().describe("Maximum tokens to return. Default is 20000. Set to null for unlimited results."),
@@ -285598,7 +285843,7 @@ var init_common2 = __esm({
285598
285843
  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.")
285599
285844
  });
285600
285845
  searchAllSchema = external_exports.object({
285601
- query: external_exports.string().describe("Search query with Elasticsearch syntax. Use quotes for exact matches, AND/OR for boolean logic, - for negation."),
285846
+ 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."),
285602
285847
  path: external_exports.string().optional().default(".").describe("Path to search in."),
285603
285848
  exact: external_exports.boolean().optional().default(false).describe("Use exact matching instead of stemming."),
285604
285849
  maxTokensPerPage: external_exports.number().optional().default(2e4).describe("Tokens per page when paginating. Default 20000."),
@@ -285705,7 +285950,8 @@ var init_common2 = __esm({
285705
285950
  };
285706
285951
  }
285707
285952
  };
285708
- 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.";
285953
+ 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.';
285954
+ 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.';
285709
285955
  queryDescription = "Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.";
285710
285956
  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.";
285711
285957
  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.";
@@ -351025,6 +351271,7 @@ function generateSandboxGlobals(options) {
351025
351271
  executing.add(p5);
351026
351272
  results.push(p5);
351027
351273
  if (executing.size >= mapConcurrency) {
351274
+ console.error(`[map] Concurrency limit reached (${executing.size}/${mapConcurrency}), waiting for a slot...`);
351028
351275
  await Promise.race(executing);
351029
351276
  }
351030
351277
  }
@@ -355254,8 +355501,23 @@ __export(ProbeAgent_exports, {
355254
355501
  ENGINE_ACTIVITY_TIMEOUT_DEFAULT: () => ENGINE_ACTIVITY_TIMEOUT_DEFAULT,
355255
355502
  ENGINE_ACTIVITY_TIMEOUT_MAX: () => ENGINE_ACTIVITY_TIMEOUT_MAX,
355256
355503
  ENGINE_ACTIVITY_TIMEOUT_MIN: () => ENGINE_ACTIVITY_TIMEOUT_MIN,
355257
- ProbeAgent: () => ProbeAgent
355504
+ ProbeAgent: () => ProbeAgent,
355505
+ debugLogToolResults: () => debugLogToolResults,
355506
+ debugTruncate: () => debugTruncate
355258
355507
  });
355508
+ function debugTruncate(s5, limit = 200) {
355509
+ if (s5.length <= limit) return s5;
355510
+ const half = Math.floor(limit / 2);
355511
+ return s5.substring(0, half) + ` ... [${s5.length} chars] ... ` + s5.substring(s5.length - half);
355512
+ }
355513
+ function debugLogToolResults(toolResults) {
355514
+ if (!toolResults || toolResults.length === 0) return;
355515
+ for (const tr of toolResults) {
355516
+ const argsStr = JSON.stringify(tr.args || {});
355517
+ const resultStr = typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result || "");
355518
+ console.log(`[DEBUG] tool: ${tr.toolName} | args: ${debugTruncate(argsStr)} | result: ${debugTruncate(resultStr)}`);
355519
+ }
355520
+ }
355259
355521
  var import_dotenv, import_anthropic2, import_openai2, import_google2, import_ai4, import_crypto8, import_events4, import_fs10, import_promises6, import_path15, ENGINE_ACTIVITY_TIMEOUT_DEFAULT, ENGINE_ACTIVITY_TIMEOUT_MIN, ENGINE_ACTIVITY_TIMEOUT_MAX, MAX_TOOL_ITERATIONS, MAX_HISTORY_MESSAGES, MAX_IMAGE_FILE_SIZE, ProbeAgent;
355260
355522
  var init_ProbeAgent = __esm({
355261
355523
  "src/agent/ProbeAgent.js"() {
@@ -355375,6 +355637,7 @@ var init_ProbeAgent = __esm({
355375
355637
  this.enableExecutePlan = !!options.enableExecutePlan;
355376
355638
  this.debug = options.debug || process.env.DEBUG === "1";
355377
355639
  this.cancelled = false;
355640
+ this._abortController = new AbortController();
355378
355641
  this.tracer = options.tracer || null;
355379
355642
  this.outline = !!options.outline;
355380
355643
  this.searchDelegate = options.searchDelegate !== void 0 ? !!options.searchDelegate : true;
@@ -355403,6 +355666,7 @@ var init_ProbeAgent = __esm({
355403
355666
  this.completionPrompt = options.completionPrompt || null;
355404
355667
  this.thinkingEffort = options.thinkingEffort || null;
355405
355668
  const effectiveAllowedTools = options.disableTools ? [] : options.allowedTools;
355669
+ this._rawAllowedTools = options.allowedTools;
355406
355670
  this.allowedTools = this._parseAllowedTools(effectiveAllowedTools);
355407
355671
  this.storageAdapter = options.storageAdapter || new InMemoryStorageAdapter();
355408
355672
  this.hooks = new HookManager();
@@ -355545,6 +355809,16 @@ var init_ProbeAgent = __esm({
355545
355809
  _filterMcpTools(mcpToolNames) {
355546
355810
  return mcpToolNames.filter((toolName) => this._isMcpToolAllowed(toolName));
355547
355811
  }
355812
+ /**
355813
+ * Check if query tool was explicitly listed in allowedTools (not via wildcard).
355814
+ * Query (ast-grep) is excluded by default because models struggle with AST pattern syntax.
355815
+ * @returns {boolean}
355816
+ * @private
355817
+ */
355818
+ _isQueryExplicitlyAllowed() {
355819
+ if (!this._rawAllowedTools) return false;
355820
+ return Array.isArray(this._rawAllowedTools) && this._rawAllowedTools.includes("query");
355821
+ }
355548
355822
  /**
355549
355823
  * Check if tracer is AppTracer (expects sessionId as first param) vs SimpleAppTracer
355550
355824
  * @returns {boolean} - True if tracer is AppTracer style (requires sessionId)
@@ -355820,6 +356094,8 @@ var init_ProbeAgent = __esm({
355820
356094
  searchDelegateModel: this.searchDelegateModel,
355821
356095
  delegationManager: this.delegationManager,
355822
356096
  // Per-instance delegation limits
356097
+ parentAbortSignal: this._abortController.signal,
356098
+ // Propagate cancellation to delegations
355823
356099
  outputBuffer: this._outputBuffer,
355824
356100
  concurrencyLimiter: this.concurrencyLimiter,
355825
356101
  // Global AI concurrency limiter
@@ -355836,7 +356112,7 @@ var init_ProbeAgent = __esm({
355836
356112
  if (wrappedTools.searchToolInstance && isToolAllowed("search")) {
355837
356113
  this.toolImplementations.search = wrappedTools.searchToolInstance;
355838
356114
  }
355839
- if (wrappedTools.queryToolInstance && isToolAllowed("query")) {
356115
+ if (wrappedTools.queryToolInstance && isToolAllowed("query") && this._isQueryExplicitlyAllowed()) {
355840
356116
  this.toolImplementations.query = wrappedTools.queryToolInstance;
355841
356117
  }
355842
356118
  if (wrappedTools.extractToolInstance && isToolAllowed("extract")) {
@@ -356267,6 +356543,15 @@ var init_ProbeAgent = __esm({
356267
356543
  }
356268
356544
  const controller = new AbortController();
356269
356545
  const timeoutState = { timeoutId: null };
356546
+ if (this._abortController.signal.aborted) {
356547
+ controller.abort();
356548
+ } else {
356549
+ const onAgentAbort = () => controller.abort();
356550
+ this._abortController.signal.addEventListener("abort", onAgentAbort, { once: true });
356551
+ controller.signal.addEventListener("abort", () => {
356552
+ this._abortController.signal.removeEventListener("abort", onAgentAbort);
356553
+ }, { once: true });
356554
+ }
356270
356555
  if (this.maxOperationTimeout && this.maxOperationTimeout > 0) {
356271
356556
  timeoutState.timeoutId = setTimeout(() => {
356272
356557
  controller.abort();
@@ -356570,7 +356855,8 @@ var init_ProbeAgent = __esm({
356570
356855
  allowEdit: this.allowEdit,
356571
356856
  allowedTools: allowedToolsForDelegate,
356572
356857
  debug: this.debug,
356573
- tracer: this.tracer
356858
+ tracer: this.tracer,
356859
+ parentAbortSignal: this._abortController.signal
356574
356860
  };
356575
356861
  if (this.debug) {
356576
356862
  console.log(`[DEBUG] Executing delegate tool`);
@@ -356765,12 +357051,13 @@ var init_ProbeAgent = __esm({
356765
357051
  const toolMap = {
356766
357052
  search: {
356767
357053
  schema: searchSchema,
356768
- description: "Search code in the repository using keyword queries with Elasticsearch syntax."
356769
- },
356770
- query: {
356771
- schema: querySchema,
356772
- description: "Search code using ast-grep structural pattern matching."
357054
+ 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."
356773
357055
  },
357056
+ // query tool (ast-grep) removed from AI-facing tools — models struggle with pattern syntax
357057
+ // query: {
357058
+ // schema: querySchema,
357059
+ // description: 'Search code using ast-grep structural pattern matching.'
357060
+ // },
356774
357061
  extract: {
356775
357062
  schema: extractSchema,
356776
357063
  description: "Extract code blocks from files based on file paths and optional line numbers."
@@ -357438,25 +357725,27 @@ ${this.architectureContext.content}
357438
357725
  } else {
357439
357726
  systemPrompt += predefinedPrompts["code-explorer"] + "\n\n";
357440
357727
  }
357728
+ 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.";
357441
357729
  systemPrompt += `You have access to powerful code search and analysis tools through MCP:
357442
- - search: Find code patterns using semantic search
357730
+ ${searchToolDesc1}
357443
357731
  - extract: Extract specific code sections with context
357444
- - query: Use AST patterns for structural code matching
357445
357732
  - listFiles: Browse directory contents
357446
357733
  - searchFiles: Find files by name patterns`;
357447
357734
  if (this.enableBash) {
357448
357735
  systemPrompt += `
357449
357736
  - bash: Execute bash commands for system operations`;
357450
357737
  }
357451
- const searchGuidance = this.searchDelegate ? "1. Start with search to retrieve extracted code blocks" : "1. Start with search to find relevant code patterns";
357452
- 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";
357738
+ 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.";
357739
+ 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";
357453
357740
  systemPrompt += `
357454
357741
 
357455
357742
  When exploring code:
357456
- ${searchGuidance}
357457
- ${extractGuidance}
357743
+ ${searchGuidance1}
357744
+ ${extractGuidance1}
357458
357745
  3. Prefer focused, specific searches over broad queries
357459
- 4. Combine multiple tools to build complete understanding`;
357746
+ 4. Do NOT repeat the same search or try trivial keyword variations \u2014 probe handles stemming and case variations automatically
357747
+ 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
357748
+ 6. Combine multiple tools to build complete understanding`;
357460
357749
  if (this.allowedFolders && this.allowedFolders.length > 0) {
357461
357750
  systemPrompt += `
357462
357751
 
@@ -357491,25 +357780,27 @@ Workspace: ${this.allowedFolders.join(", ")}`;
357491
357780
  } else {
357492
357781
  systemPrompt += predefinedPrompts["code-explorer"] + "\n\n";
357493
357782
  }
357783
+ 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.";
357494
357784
  systemPrompt += `You have access to powerful code search and analysis tools through MCP:
357495
- - search: Find code patterns using semantic search
357785
+ ${searchToolDesc2}
357496
357786
  - extract: Extract specific code sections with context
357497
- - query: Use AST patterns for structural code matching
357498
357787
  - listFiles: Browse directory contents
357499
357788
  - searchFiles: Find files by name patterns`;
357500
357789
  if (this.enableBash) {
357501
357790
  systemPrompt += `
357502
357791
  - bash: Execute bash commands for system operations`;
357503
357792
  }
357504
- const searchGuidance = this.searchDelegate ? "1. Start with search to retrieve extracted code blocks" : "1. Start with search to find relevant code patterns";
357505
- 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";
357793
+ 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.";
357794
+ 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";
357506
357795
  systemPrompt += `
357507
357796
 
357508
357797
  When exploring code:
357509
- ${searchGuidance}
357510
- ${extractGuidance}
357798
+ ${searchGuidance2}
357799
+ ${extractGuidance2}
357511
357800
  3. Prefer focused, specific searches over broad queries
357512
- 4. Combine multiple tools to build complete understanding`;
357801
+ 4. Do NOT repeat the same search or try trivial keyword variations \u2014 probe handles stemming and case variations automatically
357802
+ 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
357803
+ 6. Combine multiple tools to build complete understanding`;
357513
357804
  if (this.allowedFolders && this.allowedFolders.length > 0) {
357514
357805
  systemPrompt += `
357515
357806
 
@@ -357560,10 +357851,10 @@ Workspace: ${this.allowedFolders.join(", ")}`;
357560
357851
  Follow these instructions carefully:
357561
357852
  1. Analyze the user's request.
357562
357853
  2. Use the available tools step-by-step to fulfill the request.
357563
- 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."}
357854
+ 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."}
357564
357855
  4. Ensure to get really deep and understand the full picture before answering.
357565
357856
  5. Once the task is fully completed, use the attempt_completion tool to provide the final result.
357566
- 6. Prefer concise and focused search queries. Use specific keywords and phrases to narrow down results.${this.allowEdit ? `
357857
+ 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 ? `
357567
357858
  7. When modifying files, choose the appropriate tool:
357568
357859
  - Use 'edit' for all code modifications:
357569
357860
  * 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.
@@ -357887,6 +358178,10 @@ You are working with a workspace. Available paths: ${workspaceDesc}
357887
358178
  completionResult = result;
357888
358179
  completionAttempted = true;
357889
358180
  }, toolContext);
358181
+ if (this.debug) {
358182
+ const toolNames = Object.keys(tools2);
358183
+ console.log(`[DEBUG] Agent tools registered (${toolNames.length}): ${toolNames.join(", ")}`);
358184
+ }
357890
358185
  let maxResponseTokens = this.maxResponseTokens;
357891
358186
  if (!maxResponseTokens) {
357892
358187
  maxResponseTokens = 4e3;
@@ -357926,6 +358221,7 @@ You are working with a workspace. Available paths: ${workspaceDesc}
357926
358221
  }
357927
358222
  if (this.debug) {
357928
358223
  console.log(`[DEBUG] Step ${currentIteration}/${maxIterations} finished (reason: ${finishReason}, tools: ${toolResults?.length || 0})`);
358224
+ debugLogToolResults(toolResults);
357929
358225
  }
357930
358226
  }
357931
358227
  };
@@ -358082,6 +358378,7 @@ Double-check your response based on the criteria above. If everything looks good
358082
358378
  }
358083
358379
  if (this.debug) {
358084
358380
  console.log(`[DEBUG] Completion prompt step finished (reason: ${finishReason}, tools: ${toolResults?.length || 0})`);
358381
+ debugLogToolResults(toolResults);
358085
358382
  }
358086
358383
  }
358087
358384
  };
@@ -358792,6 +359089,9 @@ Convert your previous response content into actual JSON data that follows this s
358792
359089
  * Clean up resources (including MCP connections)
358793
359090
  */
358794
359091
  async cleanup() {
359092
+ if (!this._abortController.signal.aborted) {
359093
+ this._abortController.abort();
359094
+ }
358795
359095
  if (this.mcpBridge) {
358796
359096
  try {
358797
359097
  await this.mcpBridge.cleanup();
@@ -358815,14 +359115,25 @@ Convert your previous response content into actual JSON data that follows this s
358815
359115
  this.clearHistory();
358816
359116
  }
358817
359117
  /**
358818
- * Cancel the current request
359118
+ * Cancel the current request and all in-flight delegations.
359119
+ * Aborts the internal AbortController so streamText, subagents,
359120
+ * and any code checking the signal will stop.
358819
359121
  */
358820
359122
  cancel() {
358821
359123
  this.cancelled = true;
359124
+ this._abortController.abort();
358822
359125
  if (this.debug) {
358823
359126
  console.log(`[DEBUG] Agent cancelled for session ${this.sessionId}`);
358824
359127
  }
358825
359128
  }
359129
+ /**
359130
+ * Get the abort signal for this agent.
359131
+ * Delegations and subagents should check this signal.
359132
+ * @returns {AbortSignal}
359133
+ */
359134
+ get abortSignal() {
359135
+ return this._abortController.signal;
359136
+ }
358826
359137
  };
358827
359138
  }
358828
359139
  });
@@ -358855,12 +359166,17 @@ async function delegate({
358855
359166
  mcpConfigPath = null,
358856
359167
  delegationManager = null,
358857
359168
  // Optional per-instance manager, falls back to default singleton
358858
- concurrencyLimiter = null
359169
+ concurrencyLimiter = null,
358859
359170
  // Optional global AI concurrency limiter
359171
+ parentAbortSignal = null
359172
+ // Optional AbortSignal from parent to cancel this delegation
358860
359173
  }) {
358861
359174
  if (!task || typeof task !== "string") {
358862
359175
  throw new Error("Task parameter is required and must be a string");
358863
359176
  }
359177
+ if (parentAbortSignal?.aborted) {
359178
+ throw new Error("Delegation cancelled: parent operation was aborted");
359179
+ }
358864
359180
  const hasExplicitTimeout = Object.prototype.hasOwnProperty.call(arguments?.[0] ?? {}, "timeout");
358865
359181
  if (!hasExplicitTimeout) {
358866
359182
  const envTimeoutMs = parseInt(process.env.DELEGATION_TIMEOUT_MS || "", 10);
@@ -358945,12 +359261,37 @@ async function delegate({
358945
359261
  }
358946
359262
  const timeoutPromise = new Promise((_, reject2) => {
358947
359263
  timeoutId = setTimeout(() => {
359264
+ subagent.cancel();
358948
359265
  reject2(new Error(`Delegation timed out after ${timeout} seconds`));
358949
359266
  }, timeout * 1e3);
358950
359267
  });
359268
+ let parentAbortHandler;
359269
+ const parentAbortPromise = new Promise((_, reject2) => {
359270
+ if (parentAbortSignal) {
359271
+ if (parentAbortSignal.aborted) {
359272
+ subagent.cancel();
359273
+ reject2(new Error("Delegation cancelled: parent operation was aborted"));
359274
+ return;
359275
+ }
359276
+ parentAbortHandler = () => {
359277
+ subagent.cancel();
359278
+ reject2(new Error("Delegation cancelled: parent operation was aborted"));
359279
+ };
359280
+ parentAbortSignal.addEventListener("abort", parentAbortHandler, { once: true });
359281
+ }
359282
+ });
358951
359283
  const answerOptions = schema ? { schema } : void 0;
358952
359284
  const answerPromise = answerOptions ? subagent.answer(task, [], answerOptions) : subagent.answer(task);
358953
- const response = await Promise.race([answerPromise, timeoutPromise]);
359285
+ const racers = [answerPromise, timeoutPromise];
359286
+ if (parentAbortSignal) racers.push(parentAbortPromise);
359287
+ let response;
359288
+ try {
359289
+ response = await Promise.race(racers);
359290
+ } finally {
359291
+ if (parentAbortHandler && parentAbortSignal) {
359292
+ parentAbortSignal.removeEventListener("abort", parentAbortHandler);
359293
+ }
359294
+ }
358954
359295
  if (timeoutId !== null) {
358955
359296
  clearTimeout(timeoutId);
358956
359297
  timeoutId = null;
@@ -359106,10 +359447,9 @@ var init_delegate = __esm({
359106
359447
  if (this.tryAcquire(parentSessionId)) {
359107
359448
  return true;
359108
359449
  }
359109
- if (debug) {
359110
- console.error(`[DelegationManager] Slot unavailable (${this.globalActive}/${this.maxConcurrent}), queuing... (queue size: ${this.waitQueue.length}, timeout: ${effectiveTimeout}ms)`);
359111
- }
359450
+ console.error(`[DelegationManager] Slot unavailable (${this.globalActive}/${this.maxConcurrent}), queuing... (queue size: ${this.waitQueue.length + 1}, timeout: ${effectiveTimeout}ms)`);
359112
359451
  return new Promise((resolve8, reject2) => {
359452
+ const queuedAt = Date.now();
359113
359453
  const entry = {
359114
359454
  resolve: null,
359115
359455
  // Will be wrapped below
@@ -359117,20 +359457,23 @@ var init_delegate = __esm({
359117
359457
  // Will be wrapped below
359118
359458
  parentSessionId,
359119
359459
  debug,
359120
- queuedAt: Date.now(),
359121
- timeoutId: null
359460
+ queuedAt,
359461
+ timeoutId: null,
359462
+ reminderId: null
359122
359463
  };
359123
359464
  let settled = false;
359124
359465
  entry.resolve = (value) => {
359125
359466
  if (settled) return;
359126
359467
  settled = true;
359127
359468
  if (entry.timeoutId) clearTimeout(entry.timeoutId);
359469
+ if (entry.reminderId) clearInterval(entry.reminderId);
359128
359470
  resolve8(value);
359129
359471
  };
359130
359472
  entry.reject = (error2) => {
359131
359473
  if (settled) return;
359132
359474
  settled = true;
359133
359475
  if (entry.timeoutId) clearTimeout(entry.timeoutId);
359476
+ if (entry.reminderId) clearInterval(entry.reminderId);
359134
359477
  reject2(error2);
359135
359478
  };
359136
359479
  if (effectiveTimeout > 0) {
@@ -359142,6 +359485,13 @@ var init_delegate = __esm({
359142
359485
  entry.reject(new Error(`Delegation queue timeout: waited ${effectiveTimeout}ms for an available slot`));
359143
359486
  }, effectiveTimeout);
359144
359487
  }
359488
+ entry.reminderId = setInterval(() => {
359489
+ const waitedSeconds = Math.round((Date.now() - queuedAt) / 1e3);
359490
+ console.error(`[DelegationManager] Still waiting for slot (${waitedSeconds}s). ${this.globalActive}/${this.maxConcurrent} active, ${this.waitQueue.length} queued.`);
359491
+ }, 15e3);
359492
+ if (entry.reminderId.unref) {
359493
+ entry.reminderId.unref();
359494
+ }
359145
359495
  this.waitQueue.push(entry);
359146
359496
  });
359147
359497
  }
@@ -359180,18 +359530,14 @@ var init_delegate = __esm({
359180
359530
  const sessionData = this.sessionDelegations.get(parentSessionId);
359181
359531
  const sessionCount = sessionData?.count || 0;
359182
359532
  if (sessionCount >= this.maxPerSession) {
359183
- if (debug) {
359184
- console.error(`[DelegationManager] Session limit (${this.maxPerSession}) reached for queued item, rejecting`);
359185
- }
359533
+ console.error(`[DelegationManager] Session limit (${this.maxPerSession}) reached for queued item, rejecting`);
359186
359534
  toReject.push({ reject: reject2, error: new Error(`Maximum delegations per session (${this.maxPerSession}) reached for session ${parentSessionId}`) });
359187
359535
  continue;
359188
359536
  }
359189
359537
  }
359190
359538
  this._incrementCounters(parentSessionId);
359191
- if (debug) {
359192
- const waitTime = Date.now() - queuedAt;
359193
- console.error(`[DelegationManager] Granted slot from queue (waited ${waitTime}ms). Active: ${this.globalActive}/${this.maxConcurrent}`);
359194
- }
359539
+ const waitTime = Date.now() - queuedAt;
359540
+ console.error(`[DelegationManager] Granted slot from queue (waited ${waitTime}ms). Active: ${this.globalActive}/${this.maxConcurrent}`);
359195
359541
  toResolve.push(resolve8);
359196
359542
  }
359197
359543
  if (toResolve.length > 0 || toReject.length > 0) {
@@ -359241,6 +359587,9 @@ var init_delegate = __esm({
359241
359587
  if (entry.timeoutId) {
359242
359588
  clearTimeout(entry.timeoutId);
359243
359589
  }
359590
+ if (entry.reminderId) {
359591
+ clearInterval(entry.reminderId);
359592
+ }
359244
359593
  if (entry.reject) {
359245
359594
  entry.reject(new Error("DelegationManager was cleaned up"));
359246
359595
  }
@@ -359363,8 +359712,9 @@ Instructions:
359363
359712
  promptType: "code-researcher",
359364
359713
  allowedTools: ["extract"],
359365
359714
  maxIterations: 5,
359366
- delegationManager: options.delegationManager
359715
+ delegationManager: options.delegationManager,
359367
359716
  // Per-instance delegation limits
359717
+ parentAbortSignal: options.parentAbortSignal || null
359368
359718
  // timeout removed - inherit default from delegate (300s)
359369
359719
  });
359370
359720
  return { chunk, result };
@@ -359384,16 +359734,12 @@ async function processChunksParallel(chunks, extractionPrompt, maxWorkers, optio
359384
359734
  return result;
359385
359735
  });
359386
359736
  active.add(promise);
359387
- if (options.debug) {
359388
- console.error(`[analyze_all] Started processing chunk ${chunk.id}/${chunk.total}`);
359389
- }
359737
+ console.error(`[analyze_all] Started processing chunk ${chunk.id}/${chunk.total}`);
359390
359738
  }
359391
359739
  if (active.size > 0) {
359392
359740
  const result = await Promise.race(active);
359393
359741
  results.push(result);
359394
- if (options.debug) {
359395
- console.error(`[analyze_all] Completed chunk ${result.chunk.id}/${result.chunk.total}`);
359396
- }
359742
+ console.error(`[analyze_all] Completed chunk ${result.chunk.id}/${result.chunk.total}`);
359397
359743
  }
359398
359744
  }
359399
359745
  results.sort((a5, b5) => a5.chunk.id - b5.chunk.id);
@@ -359463,8 +359809,9 @@ Organize all findings into clear categories with items listed under each.${compl
359463
359809
  promptType: "code-researcher",
359464
359810
  allowedTools: [],
359465
359811
  maxIterations: 5,
359466
- delegationManager: options.delegationManager
359812
+ delegationManager: options.delegationManager,
359467
359813
  // Per-instance delegation limits
359814
+ parentAbortSignal: options.parentAbortSignal || null
359468
359815
  // timeout removed - inherit default from delegate (300s)
359469
359816
  });
359470
359817
  return result;
@@ -359528,8 +359875,9 @@ CRITICAL: Do NOT guess keywords. Actually run searches and see what returns resu
359528
359875
  promptType: "code-researcher",
359529
359876
  // Full tool access for exploration and experimentation
359530
359877
  maxIterations: 15,
359531
- delegationManager: options.delegationManager
359878
+ delegationManager: options.delegationManager,
359532
359879
  // Per-instance delegation limits
359880
+ parentAbortSignal: options.parentAbortSignal || null
359533
359881
  // timeout removed - inherit default from delegate (300s)
359534
359882
  });
359535
359883
  const plan = parsePlanningResult(stripResultTags(result));
@@ -359586,8 +359934,9 @@ When done, use the attempt_completion tool with your answer as the result.`;
359586
359934
  promptType: "code-researcher",
359587
359935
  allowedTools: [],
359588
359936
  maxIterations: 5,
359589
- delegationManager: options.delegationManager
359937
+ delegationManager: options.delegationManager,
359590
359938
  // Per-instance delegation limits
359939
+ parentAbortSignal: options.parentAbortSignal || null
359591
359940
  // timeout removed - inherit default from delegate (300s)
359592
359941
  });
359593
359942
  return stripResultTags(result);
@@ -359900,11 +360249,41 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
359900
360249
  "- extract: Verify code snippets to ensure targets are actually relevant before including them.",
359901
360250
  "- listFiles: Understand directory structure to find where relevant code might live.",
359902
360251
  "",
359903
- "Strategy for complex queries:",
360252
+ "CRITICAL - How probe search works (do NOT ignore):",
360253
+ "- By default (exact=false), probe ALREADY handles stemming, case-insensitive matching, and camelCase/snake_case splitting.",
360254
+ '- Searching "allowed_ips" ALREADY matches "AllowedIPs", "allowedIps", "allowed_ips", etc. Do NOT manually try case/style variations.',
360255
+ '- Searching "getUserData" ALREADY matches "get", "user", "data" and their variations.',
360256
+ "- NEVER repeat the same search query \u2014 you will get the same results.",
360257
+ "- NEVER search trivial variations of the same keyword (e.g., AllowedIPs then allowedIps then allowed_ips). This is wasteful \u2014 probe handles it.",
360258
+ "- 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.",
360259
+ "- If 2-3 consecutive searches return no results for a concept, STOP searching for it and move on.",
360260
+ "",
360261
+ "GOOD search strategy (do this):",
360262
+ ' Query: "How does authentication work and how are sessions managed?"',
360263
+ ' \u2192 search "authentication" \u2192 search "session management" (two different concepts)',
360264
+ ' Query: "Find the IP allowlist middleware"',
360265
+ ' \u2192 search "allowlist middleware" (one search, probe handles IP/ip/Ip variations)',
360266
+ ' Query: "How does BM25 scoring work with SIMD optimization?"',
360267
+ ' \u2192 search "BM25 scoring" \u2192 search "SIMD optimization" (two different concepts)',
360268
+ "",
360269
+ "BAD search strategy (never do this):",
360270
+ ' \u2192 search "AllowedIPs" \u2192 search "allowedIps" \u2192 search "allowed_ips" (WRONG: these are trivial case variations, probe handles them)',
360271
+ ' \u2192 search "CIDR" \u2192 search "cidr" \u2192 search "Cidr" \u2192 search "*cidr*" (WRONG: same keyword repeated with variations)',
360272
+ ' \u2192 search "error handling" \u2192 search "error handling" \u2192 search "error handling" (WRONG: repeating exact same query)',
360273
+ "",
360274
+ "Keyword tips:",
360275
+ "- Common programming keywords are filtered as stopwords when unquoted: function, class, return, new, struct, impl, var, let, const, etc.",
360276
+ '- Avoid searching for these alone \u2014 combine with a specific term (e.g., "middleware function" is fine, "function" alone is too generic).',
360277
+ '- To bypass stopword filtering: wrap terms in quotes ("return", "struct") or set exact=true. Both disable stemming and splitting too.',
360278
+ "- Multiple words without operators use OR logic: foo bar = foo OR bar. Use AND explicitly if you need both: foo AND bar.",
360279
+ '- camelCase terms are split: getUserData becomes "get", "user", "data" \u2014 so one search covers all naming styles.',
360280
+ "",
360281
+ "Strategy:",
359904
360282
  "1. Analyze the query - identify key concepts, entities, and relationships",
359905
- '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)',
359906
- "3. Use extract to verify relevance of promising results",
359907
- "4. Combine all relevant targets in your final response",
360283
+ "2. Run ONE focused search per concept with the most natural keyword. Trust probe to handle variations.",
360284
+ "3. If a search returns results, use extract to verify relevance",
360285
+ "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)",
360286
+ "5. Combine all relevant targets in your final response",
359908
360287
  "",
359909
360288
  `Query: ${searchQuery}`,
359910
360289
  `Search path(s): ${searchPath}`,
@@ -359957,9 +360336,12 @@ var init_vercel = __esm({
359957
360336
  }
359958
360337
  return result;
359959
360338
  };
360339
+ const previousSearches = /* @__PURE__ */ new Set();
360340
+ const paginationCounts = /* @__PURE__ */ new Map();
360341
+ const MAX_PAGES_PER_QUERY = 3;
359960
360342
  return (0, import_ai5.tool)({
359961
360343
  name: "search",
359962
- description: searchDelegate ? `${searchDescription} (delegates code search to a subagent and returns extracted code blocks)` : searchDescription,
360344
+ description: searchDelegate ? searchDelegateDescription : searchDescription,
359963
360345
  inputSchema: searchSchema,
359964
360346
  execute: async ({ query: searchQuery, path: path9, allow_tests, exact, maxTokens: paramMaxTokens, language, session, nextPage }) => {
359965
360347
  const effectiveMaxTokens = paramMaxTokens || maxTokens;
@@ -359997,6 +360379,26 @@ var init_vercel = __esm({
359997
360379
  return await search(searchOptions);
359998
360380
  };
359999
360381
  if (!searchDelegate) {
360382
+ const searchKey = `${searchQuery}::${searchPath}::${exact || false}`;
360383
+ if (!nextPage) {
360384
+ if (previousSearches.has(searchKey)) {
360385
+ if (debug) {
360386
+ console.error(`[DEDUP] Blocked duplicate search: "${searchQuery}" in "${searchPath}"`);
360387
+ }
360388
+ 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.";
360389
+ }
360390
+ previousSearches.add(searchKey);
360391
+ paginationCounts.set(searchKey, 0);
360392
+ } else {
360393
+ const pageCount = (paginationCounts.get(searchKey) || 0) + 1;
360394
+ paginationCounts.set(searchKey, pageCount);
360395
+ if (pageCount > MAX_PAGES_PER_QUERY) {
360396
+ if (debug) {
360397
+ console.error(`[DEDUP] Blocked excessive pagination (page ${pageCount}/${MAX_PAGES_PER_QUERY}): "${searchQuery}" in "${searchPath}"`);
360398
+ }
360399
+ 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.`;
360400
+ }
360401
+ }
360000
360402
  try {
360001
360403
  const result = maybeAnnotate(await runRawSearch());
360002
360404
  if (options.fileTracker && typeof result === "string") {
@@ -360035,7 +360437,8 @@ var init_vercel = __esm({
360035
360437
  promptType: "code-searcher",
360036
360438
  allowedTools: ["search", "extract", "listFiles", "attempt_completion"],
360037
360439
  searchDelegate: false,
360038
- schema: CODE_SEARCH_SCHEMA
360440
+ schema: CODE_SEARCH_SCHEMA,
360441
+ parentAbortSignal: options.parentAbortSignal || null
360039
360442
  });
360040
360443
  const delegateResult = options.tracer?.withSpan ? await options.tracer.withSpan("search.delegate", runDelegation, {
360041
360444
  "search.query": searchQuery,
@@ -360249,7 +360652,7 @@ var init_vercel = __esm({
360249
360652
  name: "delegate",
360250
360653
  description: delegateDescription,
360251
360654
  inputSchema: delegateSchema,
360252
- execute: async ({ task, currentIteration, maxIterations, parentSessionId, path: path9, provider, model, tracer, searchDelegate }) => {
360655
+ execute: async ({ task, currentIteration, maxIterations, parentSessionId, path: path9, provider, model, tracer, searchDelegate, parentAbortSignal }) => {
360253
360656
  if (!task || typeof task !== "string") {
360254
360657
  throw new Error("Task parameter is required and must be a non-empty string");
360255
360658
  }
@@ -360307,8 +360710,9 @@ var init_vercel = __esm({
360307
360710
  enableMcp,
360308
360711
  mcpConfig,
360309
360712
  mcpConfigPath,
360310
- delegationManager
360713
+ delegationManager,
360311
360714
  // Per-instance delegation limits
360715
+ parentAbortSignal
360312
360716
  });
360313
360717
  return result;
360314
360718
  }
@@ -360346,8 +360750,9 @@ var init_vercel = __esm({
360346
360750
  provider: options.provider,
360347
360751
  model: options.model,
360348
360752
  tracer: options.tracer,
360349
- delegationManager
360753
+ delegationManager,
360350
360754
  // Per-instance delegation limits
360755
+ parentAbortSignal: options.parentAbortSignal || null
360351
360756
  });
360352
360757
  return result;
360353
360758
  } catch (error2) {
@@ -397563,7 +397968,7 @@ module.exports = /*#__PURE__*/JSON.parse('{"100":"Continue","101":"Switching Pro
397563
397968
  /***/ ((module) => {
397564
397969
 
397565
397970
  "use strict";
397566
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@probelabs/visor","version":"0.1.42","main":"dist/index.js","bin":{"visor":"./dist/index.js"},"exports":{".":{"require":"./dist/index.js","import":"./dist/index.js"},"./sdk":{"types":"./dist/sdk/sdk.d.ts","import":"./dist/sdk/sdk.mjs","require":"./dist/sdk/sdk.js"},"./cli":{"require":"./dist/index.js"}},"files":["dist/","defaults/","action.yml","README.md","LICENSE"],"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"scripts":{"build:cli":"ncc build src/index.ts -o dist && cp -r defaults dist/ && cp -r output dist/ && cp -r docs dist/ && cp -r examples dist/ && cp -r src/debug-visualizer/ui dist/debug-visualizer/ && node scripts/inject-version.js && echo \'#!/usr/bin/env node\' | cat - dist/index.js > temp && mv temp dist/index.js && chmod +x dist/index.js","build:sdk":"tsup src/sdk.ts --dts --sourcemap --format esm,cjs --out-dir dist/sdk","build":"./scripts/build-oss.sh","build:ee":"npm run build:cli && npm run build:sdk","test":"jest && npm run test:yaml","test:unit":"jest","prepublishOnly":"npm run build","test:watch":"jest --watch","test:coverage":"jest --coverage","test:ee":"jest --testPathPatterns=\'tests/ee\' --testPathIgnorePatterns=\'/node_modules/\' --no-coverage","test:manual:bash":"RUN_MANUAL_TESTS=true jest tests/manual/bash-config-manual.test.ts","lint":"eslint src tests --ext .ts","lint:fix":"eslint src tests --ext .ts --fix","format":"prettier --write src tests","format:check":"prettier --check src tests","clean":"","clean:traces":"node scripts/clean-traces.js","prebuild":"npm run clean && node scripts/generate-config-schema.js","pretest":"npm run clean:traces && node scripts/generate-config-schema.js && npm run build:cli","pretest:unit":"npm run clean:traces && node scripts/generate-config-schema.js && npm run build:cli","test:with-build":"npm run build:cli && jest","test:yaml":"node dist/index.js test --progress compact","test:yaml:parallel":"node dist/index.js test --progress compact --max-parallel 4","prepare":"husky","pre-commit":"lint-staged","deploy:site":"cd site && npx wrangler pages deploy . --project-name=visor-site --commit-dirty=true","deploy:worker":"npx wrangler deploy","deploy":"npm run deploy:site && npm run deploy:worker","publish:ee":"./scripts/publish-ee.sh","release":"./scripts/release.sh","release:patch":"./scripts/release.sh patch","release:minor":"./scripts/release.sh minor","release:major":"./scripts/release.sh major","release:prerelease":"./scripts/release.sh prerelease","docs:validate":"node scripts/validate-readme-links.js","workshop:setup":"npm install -D reveal-md@6.1.2","workshop:serve":"cd workshop && reveal-md slides.md -w","workshop:export":"reveal-md workshop/slides.md --static workshop/build","workshop:pdf":"reveal-md workshop/slides.md --print workshop/Visor-Workshop.pdf --print-size letter","workshop:pdf:ci":"reveal-md workshop/slides.md --print workshop/Visor-Workshop.pdf --print-size letter --puppeteer-launch-args=\\"--no-sandbox --disable-dev-shm-usage\\"","workshop:pdf:a4":"reveal-md workshop/slides.md --print workshop/Visor-Workshop-A4.pdf --print-size A4","workshop:build":"npm run workshop:export && npm run workshop:pdf","simulate:issue":"TS_NODE_TRANSPILE_ONLY=1 ts-node scripts/simulate-gh-run.ts --event issues --action opened --debug","simulate:comment":"TS_NODE_TRANSPILE_ONLY=1 ts-node scripts/simulate-gh-run.ts --event issue_comment --action created --debug"},"keywords":["code-review","ai","github-action","cli","pr-review","visor"],"author":"Probe Labs","license":"MIT","description":"AI workflow engine for code review, assistants, and automation — orchestrate checks, MCP tools, and AI providers with YAML-driven pipelines","repository":{"type":"git","url":"git+https://github.com/probelabs/visor.git"},"bugs":{"url":"https://github.com/probelabs/visor/issues"},"homepage":"https://github.com/probelabs/visor#readme","dependencies":{"@actions/core":"^1.11.1","@apidevtools/swagger-parser":"^12.1.0","@modelcontextprotocol/sdk":"^1.25.3","@nyariv/sandboxjs":"github:probelabs/SandboxJS#f1c13b8eee98734a8ea024061eada4aa9a9ff2e9","@octokit/action":"^8.0.2","@octokit/auth-app":"^8.1.0","@octokit/core":"^7.0.3","@octokit/rest":"^22.0.0","@opentelemetry/api":"^1.9.0","@opentelemetry/core":"^1.30.1","@opentelemetry/exporter-trace-otlp-grpc":"^0.203.0","@opentelemetry/exporter-trace-otlp-http":"^0.203.0","@opentelemetry/instrumentation":"^0.203.0","@opentelemetry/resources":"^1.30.1","@opentelemetry/sdk-metrics":"^1.30.1","@opentelemetry/sdk-node":"^0.203.0","@opentelemetry/sdk-trace-base":"^1.30.1","@opentelemetry/semantic-conventions":"^1.30.1","@probelabs/probe":"^0.6.0-rc278","@types/commander":"^2.12.0","@types/uuid":"^10.0.0","acorn":"^8.16.0","acorn-walk":"^8.3.5","ajv":"^8.17.1","ajv-formats":"^3.0.1","better-sqlite3":"^11.0.0","blessed":"^0.1.81","cli-table3":"^0.6.5","commander":"^14.0.0","deepmerge":"^4.3.1","dotenv":"^17.2.3","ignore":"^7.0.5","js-yaml":"^4.1.0","jsonpath-plus":"^10.4.0","liquidjs":"^10.21.1","minimatch":"^10.2.2","node-cron":"^3.0.3","open":"^9.1.0","simple-git":"^3.28.0","uuid":"^11.1.0","ws":"^8.18.3"},"optionalDependencies":{"@anthropic/claude-code-sdk":"npm:null@*","@open-policy-agent/opa-wasm":"^1.10.0","knex":"^3.1.0","mysql2":"^3.11.0","pg":"^8.13.0","tedious":"^19.0.0"},"devDependencies":{"@eslint/js":"^9.34.0","@kie/act-js":"^2.6.2","@kie/mock-github":"^2.0.1","@swc/core":"^1.13.2","@swc/jest":"^0.2.37","@types/better-sqlite3":"^7.6.0","@types/blessed":"^0.1.27","@types/jest":"^30.0.0","@types/js-yaml":"^4.0.9","@types/node":"^24.3.0","@types/node-cron":"^3.0.11","@types/ws":"^8.18.1","@typescript-eslint/eslint-plugin":"^8.42.0","@typescript-eslint/parser":"^8.42.0","@vercel/ncc":"^0.38.4","eslint":"^9.34.0","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.4","husky":"^9.1.7","jest":"^30.1.3","lint-staged":"^16.1.6","prettier":"^3.6.2","reveal-md":"^6.1.2","ts-json-schema-generator":"^1.5.1","ts-node":"^10.9.2","tsup":"^8.5.0","typescript":"^5.9.2","wrangler":"^3.0.0"},"peerDependenciesMeta":{"@anthropic/claude-code-sdk":{"optional":true}},"directories":{"test":"tests"},"lint-staged":{"src/**/*.{ts,js}":["eslint --fix","prettier --write"],"tests/**/*.{ts,js}":["eslint --fix","prettier --write"],"*.{json,md,yml,yaml}":["prettier --write"]}}');
397971
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@probelabs/visor","version":"0.1.42","main":"dist/index.js","bin":{"visor":"./dist/index.js"},"exports":{".":{"require":"./dist/index.js","import":"./dist/index.js"},"./sdk":{"types":"./dist/sdk/sdk.d.ts","import":"./dist/sdk/sdk.mjs","require":"./dist/sdk/sdk.js"},"./cli":{"require":"./dist/index.js"}},"files":["dist/","defaults/","action.yml","README.md","LICENSE"],"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"scripts":{"build:cli":"ncc build src/index.ts -o dist && cp -r defaults dist/ && cp -r output dist/ && cp -r docs dist/ && cp -r examples dist/ && cp -r src/debug-visualizer/ui dist/debug-visualizer/ && node scripts/inject-version.js && echo \'#!/usr/bin/env node\' | cat - dist/index.js > temp && mv temp dist/index.js && chmod +x dist/index.js","build:sdk":"tsup src/sdk.ts --dts --sourcemap --format esm,cjs --out-dir dist/sdk","build":"./scripts/build-oss.sh","build:ee":"npm run build:cli && npm run build:sdk","test":"jest && npm run test:yaml","test:unit":"jest","prepublishOnly":"npm run build","test:watch":"jest --watch","test:coverage":"jest --coverage","test:ee":"jest --testPathPatterns=\'tests/ee\' --testPathIgnorePatterns=\'/node_modules/\' --no-coverage","test:manual:bash":"RUN_MANUAL_TESTS=true jest tests/manual/bash-config-manual.test.ts","lint":"eslint src tests --ext .ts","lint:fix":"eslint src tests --ext .ts --fix","format":"prettier --write src tests","format:check":"prettier --check src tests","clean":"","clean:traces":"node scripts/clean-traces.js","prebuild":"npm run clean && node scripts/generate-config-schema.js","pretest":"npm run clean:traces && node scripts/generate-config-schema.js && npm run build:cli","pretest:unit":"npm run clean:traces && node scripts/generate-config-schema.js && npm run build:cli","test:with-build":"npm run build:cli && jest","test:yaml":"node dist/index.js test --progress compact","test:yaml:parallel":"node dist/index.js test --progress compact --max-parallel 4","prepare":"husky","pre-commit":"lint-staged","deploy:site":"cd site && npx wrangler pages deploy . --project-name=visor-site --commit-dirty=true","deploy:worker":"npx wrangler deploy","deploy":"npm run deploy:site && npm run deploy:worker","publish:ee":"./scripts/publish-ee.sh","release":"./scripts/release.sh","release:patch":"./scripts/release.sh patch","release:minor":"./scripts/release.sh minor","release:major":"./scripts/release.sh major","release:prerelease":"./scripts/release.sh prerelease","docs:validate":"node scripts/validate-readme-links.js","workshop:setup":"npm install -D reveal-md@6.1.2","workshop:serve":"cd workshop && reveal-md slides.md -w","workshop:export":"reveal-md workshop/slides.md --static workshop/build","workshop:pdf":"reveal-md workshop/slides.md --print workshop/Visor-Workshop.pdf --print-size letter","workshop:pdf:ci":"reveal-md workshop/slides.md --print workshop/Visor-Workshop.pdf --print-size letter --puppeteer-launch-args=\\"--no-sandbox --disable-dev-shm-usage\\"","workshop:pdf:a4":"reveal-md workshop/slides.md --print workshop/Visor-Workshop-A4.pdf --print-size A4","workshop:build":"npm run workshop:export && npm run workshop:pdf","simulate:issue":"TS_NODE_TRANSPILE_ONLY=1 ts-node scripts/simulate-gh-run.ts --event issues --action opened --debug","simulate:comment":"TS_NODE_TRANSPILE_ONLY=1 ts-node scripts/simulate-gh-run.ts --event issue_comment --action created --debug"},"keywords":["code-review","ai","github-action","cli","pr-review","visor"],"author":"Probe Labs","license":"MIT","description":"AI workflow engine for code review, assistants, and automation — orchestrate checks, MCP tools, and AI providers with YAML-driven pipelines","repository":{"type":"git","url":"git+https://github.com/probelabs/visor.git"},"bugs":{"url":"https://github.com/probelabs/visor/issues"},"homepage":"https://github.com/probelabs/visor#readme","dependencies":{"@actions/core":"^1.11.1","@apidevtools/swagger-parser":"^12.1.0","@modelcontextprotocol/sdk":"^1.25.3","@nyariv/sandboxjs":"github:probelabs/SandboxJS#f1c13b8eee98734a8ea024061eada4aa9a9ff2e9","@octokit/action":"^8.0.2","@octokit/auth-app":"^8.1.0","@octokit/core":"^7.0.3","@octokit/rest":"^22.0.0","@opentelemetry/api":"^1.9.0","@opentelemetry/core":"^1.30.1","@opentelemetry/exporter-trace-otlp-grpc":"^0.203.0","@opentelemetry/exporter-trace-otlp-http":"^0.203.0","@opentelemetry/instrumentation":"^0.203.0","@opentelemetry/resources":"^1.30.1","@opentelemetry/sdk-metrics":"^1.30.1","@opentelemetry/sdk-node":"^0.203.0","@opentelemetry/sdk-trace-base":"^1.30.1","@opentelemetry/semantic-conventions":"^1.30.1","@probelabs/probe":"^0.6.0-rc280","@types/commander":"^2.12.0","@types/uuid":"^10.0.0","acorn":"^8.16.0","acorn-walk":"^8.3.5","ajv":"^8.17.1","ajv-formats":"^3.0.1","better-sqlite3":"^11.0.0","blessed":"^0.1.81","cli-table3":"^0.6.5","commander":"^14.0.0","deepmerge":"^4.3.1","dotenv":"^17.2.3","ignore":"^7.0.5","js-yaml":"^4.1.0","jsonpath-plus":"^10.4.0","liquidjs":"^10.21.1","minimatch":"^10.2.2","node-cron":"^3.0.3","open":"^9.1.0","simple-git":"^3.28.0","uuid":"^11.1.0","ws":"^8.18.3"},"optionalDependencies":{"@anthropic/claude-code-sdk":"npm:null@*","@open-policy-agent/opa-wasm":"^1.10.0","knex":"^3.1.0","mysql2":"^3.11.0","pg":"^8.13.0","tedious":"^19.0.0"},"devDependencies":{"@eslint/js":"^9.34.0","@kie/act-js":"^2.6.2","@kie/mock-github":"^2.0.1","@swc/core":"^1.13.2","@swc/jest":"^0.2.37","@types/better-sqlite3":"^7.6.0","@types/blessed":"^0.1.27","@types/jest":"^30.0.0","@types/js-yaml":"^4.0.9","@types/node":"^24.3.0","@types/node-cron":"^3.0.11","@types/ws":"^8.18.1","@typescript-eslint/eslint-plugin":"^8.42.0","@typescript-eslint/parser":"^8.42.0","@vercel/ncc":"^0.38.4","eslint":"^9.34.0","eslint-config-prettier":"^10.1.8","eslint-plugin-prettier":"^5.5.4","husky":"^9.1.7","jest":"^30.1.3","lint-staged":"^16.1.6","prettier":"^3.6.2","reveal-md":"^6.1.2","ts-json-schema-generator":"^1.5.1","ts-node":"^10.9.2","tsup":"^8.5.0","typescript":"^5.9.2","wrangler":"^3.0.0"},"peerDependenciesMeta":{"@anthropic/claude-code-sdk":{"optional":true}},"directories":{"test":"tests"},"lint-staged":{"src/**/*.{ts,js}":["eslint --fix","prettier --write"],"tests/**/*.{ts,js}":["eslint --fix","prettier --write"],"*.{json,md,yml,yaml}":["prettier --write"]}}');
397567
397972
 
397568
397973
  /***/ })
397569
397974