@probelabs/visor 0.1.165 → 0.1.166

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 (81) hide show
  1. package/dist/index.js +525 -120
  2. package/dist/output/traces/{run-2026-03-06T06-08-10-897Z.ndjson → run-2026-03-06T13-08-34-152Z.ndjson} +84 -84
  3. package/dist/output/traces/{run-2026-03-06T06-08-55-016Z.ndjson → run-2026-03-06T13-09-10-593Z.ndjson} +1894 -1894
  4. package/dist/pr-analyzer.d.ts +2 -1
  5. package/dist/pr-analyzer.d.ts.map +1 -1
  6. package/dist/providers/http-client-provider.d.ts.map +1 -1
  7. package/dist/sdk/{check-provider-registry-6P2KJ423.mjs → check-provider-registry-TGPICTHD.mjs} +5 -5
  8. package/dist/sdk/{check-provider-registry-TTVN3V2O.mjs → check-provider-registry-V6C4LUYJ.mjs} +5 -5
  9. package/dist/sdk/{check-provider-registry-4SHN3GSH.mjs → check-provider-registry-WXEBJWXY.mjs} +5 -5
  10. package/dist/sdk/{chunk-EO4IJNM7.mjs → chunk-E7NRUDWL.mjs} +2 -2
  11. package/dist/sdk/{chunk-LDE33FGE.mjs → chunk-HFCOZPAS.mjs} +3 -3
  12. package/dist/sdk/{chunk-LDE33FGE.mjs.map → chunk-HFCOZPAS.mjs.map} +1 -1
  13. package/dist/sdk/{chunk-XDIBL7QB.mjs → chunk-KBTFMYZQ.mjs} +2 -2
  14. package/dist/sdk/{chunk-G5JBWW3O.mjs → chunk-KKGMGB4X.mjs} +160 -20
  15. package/dist/sdk/chunk-KKGMGB4X.mjs.map +1 -0
  16. package/dist/sdk/{chunk-GMHSXC5K.mjs → chunk-OQ3CML4F.mjs} +3 -3
  17. package/dist/sdk/{chunk-J236ZVYX.mjs → chunk-RV5SK4FZ.mjs} +3 -3
  18. package/dist/sdk/{chunk-S47KBQQK.mjs → chunk-T5USZCCM.mjs} +2 -2
  19. package/dist/sdk/{chunk-S47KBQQK.mjs.map → chunk-T5USZCCM.mjs.map} +1 -1
  20. package/dist/sdk/{chunk-GOJRNYTV.mjs → chunk-WSYVK6ML.mjs} +188 -22
  21. package/dist/sdk/chunk-WSYVK6ML.mjs.map +1 -0
  22. package/dist/sdk/{chunk-MYROK4LB.mjs → chunk-ZQR4AGS3.mjs} +160 -20
  23. package/dist/sdk/chunk-ZQR4AGS3.mjs.map +1 -0
  24. package/dist/sdk/{failure-condition-evaluator-N3VNLWZD.mjs → failure-condition-evaluator-5EAESM44.mjs} +3 -3
  25. package/dist/sdk/{failure-condition-evaluator-M6SIUQF4.mjs → failure-condition-evaluator-GPANOHP2.mjs} +3 -3
  26. package/dist/sdk/{github-frontend-ATORHHF6.mjs → github-frontend-BPRRUIGB.mjs} +3 -3
  27. package/dist/sdk/{github-frontend-MHXL2Q2V.mjs → github-frontend-P274ISBJ.mjs} +3 -3
  28. package/dist/sdk/{host-JROON6IT.mjs → host-753E6PKF.mjs} +2 -2
  29. package/dist/sdk/{host-OBXKDFT7.mjs → host-AIMRV5YL.mjs} +2 -2
  30. package/dist/sdk/{routing-QCDX43XD.mjs → routing-BXHP2E62.mjs} +4 -4
  31. package/dist/sdk/{routing-TGJD66Q5.mjs → routing-QHWSMAIH.mjs} +4 -4
  32. package/dist/sdk/{schedule-tool-D5TSTGP2.mjs → schedule-tool-MQHISNJ6.mjs} +5 -5
  33. package/dist/sdk/{schedule-tool-XCGJI2VB.mjs → schedule-tool-OCZGLKMJ.mjs} +5 -5
  34. package/dist/sdk/{schedule-tool-C5QN5OQU.mjs → schedule-tool-ZVOSSFN2.mjs} +5 -5
  35. package/dist/sdk/{schedule-tool-handler-DKHHPZAG.mjs → schedule-tool-handler-4NCS4ARE.mjs} +5 -5
  36. package/dist/sdk/{schedule-tool-handler-OKZ53WMC.mjs → schedule-tool-handler-BGOL2TOP.mjs} +5 -5
  37. package/dist/sdk/{schedule-tool-handler-ZUMPNAVY.mjs → schedule-tool-handler-TZYXM664.mjs} +5 -5
  38. package/dist/sdk/sdk.js +147 -7
  39. package/dist/sdk/sdk.js.map +1 -1
  40. package/dist/sdk/sdk.mjs +4 -4
  41. package/dist/sdk/{trace-helpers-J5CJ4PUN.mjs → trace-helpers-CTHTK6V5.mjs} +2 -2
  42. package/dist/sdk/{trace-helpers-KFQJ7IAG.mjs → trace-helpers-UG6FOWVV.mjs} +2 -2
  43. package/dist/sdk/{workflow-check-provider-T6WFK4RB.mjs → workflow-check-provider-3M5LXLLX.mjs} +5 -5
  44. package/dist/sdk/{workflow-check-provider-WLUAJPAS.mjs → workflow-check-provider-QKHL6AFT.mjs} +5 -5
  45. package/dist/sdk/{workflow-check-provider-RBYA6ZGU.mjs → workflow-check-provider-UTNO6XN6.mjs} +5 -5
  46. package/dist/slack/socket-runner.d.ts +14 -0
  47. package/dist/slack/socket-runner.d.ts.map +1 -1
  48. package/dist/traces/{run-2026-03-06T06-08-10-897Z.ndjson → run-2026-03-06T13-08-34-152Z.ndjson} +84 -84
  49. package/dist/traces/{run-2026-03-06T06-08-55-016Z.ndjson → run-2026-03-06T13-09-10-593Z.ndjson} +1894 -1894
  50. package/dist/utils/oauth2-token-cache.d.ts +44 -0
  51. package/dist/utils/oauth2-token-cache.d.ts.map +1 -0
  52. package/package.json +2 -2
  53. package/dist/sdk/chunk-G5JBWW3O.mjs.map +0 -1
  54. package/dist/sdk/chunk-GOJRNYTV.mjs.map +0 -1
  55. package/dist/sdk/chunk-MYROK4LB.mjs.map +0 -1
  56. /package/dist/sdk/{check-provider-registry-4SHN3GSH.mjs.map → check-provider-registry-TGPICTHD.mjs.map} +0 -0
  57. /package/dist/sdk/{check-provider-registry-6P2KJ423.mjs.map → check-provider-registry-V6C4LUYJ.mjs.map} +0 -0
  58. /package/dist/sdk/{check-provider-registry-TTVN3V2O.mjs.map → check-provider-registry-WXEBJWXY.mjs.map} +0 -0
  59. /package/dist/sdk/{chunk-EO4IJNM7.mjs.map → chunk-E7NRUDWL.mjs.map} +0 -0
  60. /package/dist/sdk/{chunk-XDIBL7QB.mjs.map → chunk-KBTFMYZQ.mjs.map} +0 -0
  61. /package/dist/sdk/{chunk-GMHSXC5K.mjs.map → chunk-OQ3CML4F.mjs.map} +0 -0
  62. /package/dist/sdk/{chunk-J236ZVYX.mjs.map → chunk-RV5SK4FZ.mjs.map} +0 -0
  63. /package/dist/sdk/{failure-condition-evaluator-M6SIUQF4.mjs.map → failure-condition-evaluator-5EAESM44.mjs.map} +0 -0
  64. /package/dist/sdk/{failure-condition-evaluator-N3VNLWZD.mjs.map → failure-condition-evaluator-GPANOHP2.mjs.map} +0 -0
  65. /package/dist/sdk/{github-frontend-ATORHHF6.mjs.map → github-frontend-BPRRUIGB.mjs.map} +0 -0
  66. /package/dist/sdk/{github-frontend-MHXL2Q2V.mjs.map → github-frontend-P274ISBJ.mjs.map} +0 -0
  67. /package/dist/sdk/{host-JROON6IT.mjs.map → host-753E6PKF.mjs.map} +0 -0
  68. /package/dist/sdk/{host-OBXKDFT7.mjs.map → host-AIMRV5YL.mjs.map} +0 -0
  69. /package/dist/sdk/{routing-QCDX43XD.mjs.map → routing-BXHP2E62.mjs.map} +0 -0
  70. /package/dist/sdk/{routing-TGJD66Q5.mjs.map → routing-QHWSMAIH.mjs.map} +0 -0
  71. /package/dist/sdk/{schedule-tool-C5QN5OQU.mjs.map → schedule-tool-MQHISNJ6.mjs.map} +0 -0
  72. /package/dist/sdk/{schedule-tool-D5TSTGP2.mjs.map → schedule-tool-OCZGLKMJ.mjs.map} +0 -0
  73. /package/dist/sdk/{schedule-tool-XCGJI2VB.mjs.map → schedule-tool-ZVOSSFN2.mjs.map} +0 -0
  74. /package/dist/sdk/{schedule-tool-handler-DKHHPZAG.mjs.map → schedule-tool-handler-4NCS4ARE.mjs.map} +0 -0
  75. /package/dist/sdk/{schedule-tool-handler-OKZ53WMC.mjs.map → schedule-tool-handler-BGOL2TOP.mjs.map} +0 -0
  76. /package/dist/sdk/{schedule-tool-handler-ZUMPNAVY.mjs.map → schedule-tool-handler-TZYXM664.mjs.map} +0 -0
  77. /package/dist/sdk/{trace-helpers-J5CJ4PUN.mjs.map → trace-helpers-CTHTK6V5.mjs.map} +0 -0
  78. /package/dist/sdk/{trace-helpers-KFQJ7IAG.mjs.map → trace-helpers-UG6FOWVV.mjs.map} +0 -0
  79. /package/dist/sdk/{workflow-check-provider-RBYA6ZGU.mjs.map → workflow-check-provider-3M5LXLLX.mjs.map} +0 -0
  80. /package/dist/sdk/{workflow-check-provider-T6WFK4RB.mjs.map → workflow-check-provider-QKHL6AFT.mjs.map} +0 -0
  81. /package/dist/sdk/{workflow-check-provider-WLUAJPAS.mjs.map → workflow-check-provider-UTNO6XN6.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 = '0431072b';
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 = 'eff5ed2df';
6
6
  /******/ (() => { // webpackBootstrap
7
7
  /******/ var __webpack_modules__ = ({
8
8
 
@@ -178585,10 +178585,12 @@ const file_exclusion_1 = __nccwpck_require__(69342);
178585
178585
  class PRAnalyzer {
178586
178586
  octokit;
178587
178587
  maxRetries;
178588
+ baseDelay;
178588
178589
  fileExclusionHelper;
178589
- constructor(octokit, maxRetries = 3, workingDirectory = path.resolve(process.cwd())) {
178590
+ constructor(octokit, maxRetries = 3, workingDirectory = path.resolve(process.cwd()), baseDelay = 1000) {
178590
178591
  this.octokit = octokit;
178591
178592
  this.maxRetries = maxRetries;
178593
+ this.baseDelay = baseDelay;
178592
178594
  this.fileExclusionHelper = new file_exclusion_1.FileExclusionHelper(workingDirectory);
178593
178595
  }
178594
178596
  /**
@@ -178780,7 +178782,7 @@ class PRAnalyzer {
178780
178782
  }
178781
178783
  // Check if this is a retryable error
178782
178784
  if (this.isRetryableError(error)) {
178783
- const delay = Math.min(1000 * Math.pow(2, attempt), 5000); // Exponential backoff, max 5s
178785
+ const delay = Math.min(this.baseDelay * Math.pow(2, attempt), this.baseDelay * 5); // Exponential backoff
178784
178786
  await new Promise(resolve => setTimeout(resolve, delay));
178785
178787
  }
178786
178788
  else {
@@ -185511,6 +185513,7 @@ const liquid_extensions_1 = __nccwpck_require__(33042);
185511
185513
  const env_resolver_1 = __nccwpck_require__(58749);
185512
185514
  const sandbox_1 = __nccwpck_require__(12630);
185513
185515
  const template_context_1 = __nccwpck_require__(1581);
185516
+ const oauth2_token_cache_1 = __nccwpck_require__(34713);
185514
185517
  const logger_1 = __nccwpck_require__(86999);
185515
185518
  const fs = __importStar(__nccwpck_require__(79896));
185516
185519
  const path = __importStar(__nccwpck_require__(16928));
@@ -185542,13 +185545,15 @@ class HttpClientProvider extends check_provider_interface_1.CheckProvider {
185542
185545
  if (cfg.type !== 'http_client') {
185543
185546
  return false;
185544
185547
  }
185545
- // Must have URL specified
185546
- if (typeof cfg.url !== 'string' || !cfg.url) {
185548
+ // Must have either `url` or `base_url` specified
185549
+ const hasUrl = typeof cfg.url === 'string' && cfg.url;
185550
+ const hasBaseUrl = typeof cfg.base_url === 'string' && cfg.base_url;
185551
+ if (!hasUrl && !hasBaseUrl) {
185547
185552
  return false;
185548
185553
  }
185549
- // Validate URL format
185554
+ // Validate URL format (check whichever is provided)
185550
185555
  try {
185551
- new URL(cfg.url);
185556
+ new URL((hasUrl ? cfg.url : cfg.base_url));
185552
185557
  return true;
185553
185558
  }
185554
185559
  catch {
@@ -185556,13 +185561,34 @@ class HttpClientProvider extends check_provider_interface_1.CheckProvider {
185556
185561
  }
185557
185562
  }
185558
185563
  async execute(prInfo, config, dependencyResults, context) {
185559
- const url = config.url;
185564
+ const baseUrl = config.base_url;
185565
+ const rawPath = config.path;
185566
+ const pathParams = config.params || {};
185567
+ const queryParams = config.query || {};
185568
+ const authConfig = config.auth;
185569
+ // Build URL: either direct `url` or `base_url` + `path` with param substitution
185570
+ let url;
185571
+ if (baseUrl && rawPath) {
185572
+ // Substitute {param} placeholders in path
185573
+ let resolvedPath = rawPath;
185574
+ for (const [key, value] of Object.entries(pathParams)) {
185575
+ resolvedPath = resolvedPath.replace(`{${key}}`, encodeURIComponent(value));
185576
+ }
185577
+ url = `${baseUrl.replace(/\/+$/, '')}/${resolvedPath.replace(/^\/+/, '')}`;
185578
+ // Append query parameters
185579
+ if (Object.keys(queryParams).length > 0) {
185580
+ const qs = new URLSearchParams(queryParams).toString();
185581
+ url += `${url.includes('?') ? '&' : '?'}${qs}`;
185582
+ }
185583
+ }
185584
+ else {
185585
+ url = config.url;
185586
+ }
185560
185587
  const method = config.method || 'GET';
185561
185588
  const headers = config.headers || {};
185562
185589
  const timeout = config.timeout || 30000;
185563
185590
  const transform = config.transform;
185564
185591
  const transformJs = config.transform_js;
185565
- const bodyTemplate = config.body;
185566
185592
  const outputFileTemplate = config.output_file;
185567
185593
  const skipIfExists = config.skip_if_exists !== false; // Default true for caching
185568
185594
  // Track resolved URL for error messages
@@ -185583,9 +185609,14 @@ class HttpClientProvider extends check_provider_interface_1.CheckProvider {
185583
185609
  renderedUrl = await this.liquid.parseAndRender(renderedUrl, templateContext);
185584
185610
  resolvedUrlForErrors = renderedUrl; // Update after Liquid rendering
185585
185611
  }
185586
- // Prepare request body if provided
185612
+ // Prepare request body supports both Liquid template strings and JSON objects
185587
185613
  let requestBody;
185588
- if (bodyTemplate) {
185614
+ const rawBody = config.body;
185615
+ const bodyTemplate = typeof rawBody === 'string' ? rawBody : undefined;
185616
+ if (rawBody && typeof rawBody === 'object') {
185617
+ requestBody = JSON.stringify(rawBody);
185618
+ }
185619
+ else if (bodyTemplate) {
185589
185620
  // First resolve shell-style environment variables
185590
185621
  let resolvedBody = String(env_resolver_1.EnvironmentResolver.resolveValue(bodyTemplate));
185591
185622
  // Then render Liquid templates if present
@@ -185611,6 +185642,12 @@ class HttpClientProvider extends check_provider_interface_1.CheckProvider {
185611
185642
  logger_1.logger.verbose(`[http_client] ${key}: ${maskedValue}`);
185612
185643
  }
185613
185644
  }
185645
+ // Inject OAuth2 Bearer token if auth config is provided
185646
+ if (authConfig?.type === 'oauth2_client_credentials') {
185647
+ const tokenCache = oauth2_token_cache_1.OAuth2TokenCache.getInstance();
185648
+ const token = await tokenCache.getToken(authConfig);
185649
+ resolvedHeaders['Authorization'] = `Bearer ${token}`;
185650
+ }
185614
185651
  // Resolve output_file path if specified
185615
185652
  let resolvedOutputFile;
185616
185653
  if (outputFileTemplate) {
@@ -185908,6 +185945,11 @@ class HttpClientProvider extends check_provider_interface_1.CheckProvider {
185908
185945
  return [
185909
185946
  'type',
185910
185947
  'url',
185948
+ 'base_url',
185949
+ 'path',
185950
+ 'params',
185951
+ 'query',
185952
+ 'auth',
185911
185953
  'method',
185912
185954
  'headers',
185913
185955
  'body',
@@ -198589,6 +198631,9 @@ class SlackSocketRunner {
198589
198631
  genericScheduler;
198590
198632
  messageTriggerEvaluator;
198591
198633
  activeThreads = new Set();
198634
+ heartbeatTimer;
198635
+ lastPong = 0;
198636
+ closing = false; // prevent duplicate reconnects
198592
198637
  constructor(engine, cfg, opts) {
198593
198638
  const app = opts.appToken || process.env.SLACK_APP_TOKEN || '';
198594
198639
  if (!app)
@@ -198779,21 +198824,93 @@ class SlackSocketRunner {
198779
198824
  return json.url;
198780
198825
  }
198781
198826
  async connect(url) {
198782
- this.ws = new ws_1.default(url);
198783
- this.ws.on('open', () => {
198827
+ // Close previous WebSocket to prevent ghost event handlers
198828
+ this.closeWebSocket();
198829
+ const ws = new ws_1.default(url);
198830
+ this.ws = ws;
198831
+ this.closing = false;
198832
+ ws.on('open', () => {
198784
198833
  this.retryCount = 0; // Reset on successful connection
198834
+ this.lastPong = Date.now();
198785
198835
  logger_1.logger.info('[SlackSocket] WebSocket connected');
198836
+ this.startHeartbeat();
198786
198837
  });
198787
- this.ws.on('close', (code, reason) => {
198838
+ ws.on('close', (code, reason) => {
198788
198839
  logger_1.logger.warn(`[SlackSocket] WebSocket closed: ${code} ${reason}`);
198789
- setTimeout(() => this.restart().catch(() => { }), 1000);
198840
+ this.stopHeartbeat();
198841
+ // Only reconnect if this is still the active WebSocket
198842
+ if (this.ws === ws && !this.closing) {
198843
+ this.closing = true;
198844
+ setTimeout(() => this.restart(), 1000);
198845
+ }
198790
198846
  });
198791
- this.ws.on('error', err => {
198847
+ ws.on('error', err => {
198792
198848
  logger_1.logger.error(`[SlackSocket] WebSocket error: ${err}`);
198793
198849
  });
198794
- this.ws.on('message', data => this.handleMessage(data.toString()).catch(() => { }));
198850
+ ws.on('pong', () => {
198851
+ this.lastPong = Date.now();
198852
+ });
198853
+ ws.on('message', data => this.handleMessage(data.toString()).catch(() => { }));
198854
+ }
198855
+ /**
198856
+ * Close the current WebSocket connection and stop heartbeat.
198857
+ * Safe to call multiple times.
198858
+ */
198859
+ closeWebSocket() {
198860
+ this.stopHeartbeat();
198861
+ if (this.ws) {
198862
+ const old = this.ws;
198863
+ this.ws = undefined;
198864
+ try {
198865
+ // Remove listeners to prevent ghost close/message handlers
198866
+ old.removeAllListeners();
198867
+ old.close();
198868
+ }
198869
+ catch {
198870
+ // best effort
198871
+ }
198872
+ }
198873
+ }
198874
+ /**
198875
+ * Start periodic WebSocket ping to detect dead connections.
198876
+ * If no pong is received within 60s, force a reconnect.
198877
+ */
198878
+ startHeartbeat() {
198879
+ this.stopHeartbeat();
198880
+ const PING_INTERVAL_MS = 30_000; // ping every 30s
198881
+ const PONG_TIMEOUT_MS = 60_000; // dead if no pong for 60s
198882
+ this.heartbeatTimer = setInterval(() => {
198883
+ if (!this.ws || this.ws.readyState !== ws_1.default.OPEN)
198884
+ return;
198885
+ // Check if last pong is stale
198886
+ const sincePong = Date.now() - this.lastPong;
198887
+ if (this.lastPong > 0 && sincePong > PONG_TIMEOUT_MS) {
198888
+ logger_1.logger.warn(`[SlackSocket] No pong received for ${Math.round(sincePong / 1000)}s, forcing reconnect`);
198889
+ this.closeWebSocket();
198890
+ this.closing = true;
198891
+ this.restart();
198892
+ return;
198893
+ }
198894
+ try {
198895
+ this.ws.ping();
198896
+ }
198897
+ catch {
198898
+ // ping failed — connection is likely dead
198899
+ logger_1.logger.warn('[SlackSocket] Ping failed, forcing reconnect');
198900
+ this.closeWebSocket();
198901
+ this.closing = true;
198902
+ this.restart();
198903
+ }
198904
+ }, PING_INTERVAL_MS);
198905
+ }
198906
+ stopHeartbeat() {
198907
+ if (this.heartbeatTimer) {
198908
+ clearInterval(this.heartbeatTimer);
198909
+ this.heartbeatTimer = undefined;
198910
+ }
198795
198911
  }
198796
198912
  async restart() {
198913
+ this.closing = false;
198797
198914
  try {
198798
198915
  const url = await this.openConnection();
198799
198916
  await this.connect(url);
@@ -198803,7 +198920,7 @@ class SlackSocketRunner {
198803
198920
  // Exponential backoff: 2s, 4s, 8s, 16s, 32s, capped at 60s
198804
198921
  const delay = Math.min(2000 * Math.pow(2, this.retryCount - 1), 60000);
198805
198922
  logger_1.logger.error(`[SlackSocket] Restart failed (attempt ${this.retryCount}), retrying in ${Math.round(delay / 1000)}s: ${e instanceof Error ? e.message : e}`);
198806
- setTimeout(() => this.restart().catch(() => { }), delay);
198923
+ setTimeout(() => this.restart(), delay);
198807
198924
  }
198808
198925
  }
198809
198926
  send(obj) {
@@ -198881,6 +198998,16 @@ class SlackSocketRunner {
198881
198998
  return;
198882
198999
  if (env.envelope_id)
198883
199000
  this.send({ envelope_id: env.envelope_id }); // ack ASAP
199001
+ // Handle Slack disconnect events — proactively reconnect before the connection dies
199002
+ if (env.type === 'disconnect') {
199003
+ const reason = env.reason || 'unknown';
199004
+ logger_1.logger.info(`[SlackSocket] Received disconnect event (reason: ${reason}), reconnecting`);
199005
+ // Slack will close the connection shortly; proactively reconnect now
199006
+ this.closeWebSocket();
199007
+ this.closing = true;
199008
+ this.restart();
199009
+ return;
199010
+ }
198884
199011
  if (env.type !== 'events_api' || !env.payload) {
198885
199012
  if (process.env.VISOR_DEBUG === 'true') {
198886
199013
  logger_1.logger.debug(`[SlackSocket] Dropping non-events payload: type=${String(env.type || '-')}`);
@@ -199584,17 +199711,10 @@ class SlackSocketRunner {
199584
199711
  logger_1.logger.warn(`[SlackSocket] Error stopping generic scheduler: ${e instanceof Error ? e.message : e}`);
199585
199712
  }
199586
199713
  }
199587
- // Close WebSocket connection
199588
- if (this.ws) {
199589
- try {
199590
- this.ws.close();
199591
- this.ws = undefined;
199592
- logger_1.logger.info('[SlackSocket] WebSocket closed');
199593
- }
199594
- catch {
199595
- // Best effort
199596
- }
199597
- }
199714
+ // Close WebSocket connection and stop heartbeat
199715
+ this.closing = true; // prevent reconnect on close
199716
+ this.closeWebSocket();
199717
+ logger_1.logger.info('[SlackSocket] WebSocket closed');
199598
199718
  }
199599
199719
  /**
199600
199720
  * Get the scheduler instance
@@ -221188,6 +221308,130 @@ function emitMermaidFromMarkdown(checkName, markdown, origin) {
221188
221308
  }
221189
221309
 
221190
221310
 
221311
+ /***/ }),
221312
+
221313
+ /***/ 34713:
221314
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
221315
+
221316
+ "use strict";
221317
+
221318
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
221319
+ exports.OAuth2TokenCache = void 0;
221320
+ const logger_1 = __nccwpck_require__(86999);
221321
+ const env_resolver_1 = __nccwpck_require__(58749);
221322
+ /**
221323
+ * Singleton cache for OAuth2 tokens.
221324
+ *
221325
+ * - Keyed by hash(token_url + client_id) to support multiple providers
221326
+ * - Lazy refresh: only fetches when expired or near-expiry
221327
+ * - Deduplicates concurrent requests: if two calls hit with an expired token,
221328
+ * both await the same fetch promise
221329
+ */
221330
+ class OAuth2TokenCache {
221331
+ static instance;
221332
+ cache = new Map();
221333
+ static getInstance() {
221334
+ if (!OAuth2TokenCache.instance) {
221335
+ OAuth2TokenCache.instance = new OAuth2TokenCache();
221336
+ }
221337
+ return OAuth2TokenCache.instance;
221338
+ }
221339
+ /** Visible for testing */
221340
+ static resetInstance() {
221341
+ OAuth2TokenCache.instance = undefined;
221342
+ }
221343
+ /**
221344
+ * Get a valid Bearer token for the given config.
221345
+ * Returns a cached token if still valid, otherwise fetches a new one.
221346
+ */
221347
+ async getToken(config) {
221348
+ const clientId = String(env_resolver_1.EnvironmentResolver.resolveValue(config.client_id));
221349
+ const clientSecret = String(env_resolver_1.EnvironmentResolver.resolveValue(config.client_secret));
221350
+ const tokenUrl = String(env_resolver_1.EnvironmentResolver.resolveValue(config.token_url));
221351
+ const bufferMs = (config.token_ttl_buffer ?? 300) * 1000;
221352
+ const cacheKey = `${tokenUrl}|${clientId}`;
221353
+ const cached = this.cache.get(cacheKey);
221354
+ // Return cached token if still valid (with buffer)
221355
+ if (cached && cached.expires_at - bufferMs > Date.now()) {
221356
+ logger_1.logger.verbose('[oauth2] Using cached token');
221357
+ return cached.access_token;
221358
+ }
221359
+ // If another request is already refreshing, await it
221360
+ if (cached?.refreshPromise) {
221361
+ logger_1.logger.verbose('[oauth2] Awaiting in-flight token refresh');
221362
+ return cached.refreshPromise;
221363
+ }
221364
+ // Fetch a new token
221365
+ const refreshPromise = this.fetchToken(tokenUrl, clientId, clientSecret, config.scopes);
221366
+ // Store the promise so concurrent callers share it
221367
+ if (cached) {
221368
+ cached.refreshPromise = refreshPromise;
221369
+ }
221370
+ else {
221371
+ this.cache.set(cacheKey, {
221372
+ access_token: '',
221373
+ expires_at: 0,
221374
+ refreshPromise,
221375
+ });
221376
+ }
221377
+ try {
221378
+ const token = await refreshPromise;
221379
+ return token;
221380
+ }
221381
+ finally {
221382
+ // Clear the in-flight promise regardless of outcome
221383
+ const entry = this.cache.get(cacheKey);
221384
+ if (entry) {
221385
+ entry.refreshPromise = undefined;
221386
+ }
221387
+ }
221388
+ }
221389
+ async fetchToken(tokenUrl, clientId, clientSecret, scopes) {
221390
+ logger_1.logger.verbose(`[oauth2] Fetching token from ${tokenUrl}`);
221391
+ const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
221392
+ const bodyParams = new URLSearchParams({ grant_type: 'client_credentials' });
221393
+ if (scopes?.length) {
221394
+ bodyParams.set('scope', scopes.join(' '));
221395
+ }
221396
+ const response = await fetch(tokenUrl, {
221397
+ method: 'POST',
221398
+ headers: {
221399
+ 'Content-Type': 'application/x-www-form-urlencoded',
221400
+ Authorization: `Basic ${credentials}`,
221401
+ },
221402
+ body: bodyParams.toString(),
221403
+ });
221404
+ if (!response.ok) {
221405
+ let errorDetail = '';
221406
+ try {
221407
+ errorDetail = await response.text();
221408
+ }
221409
+ catch { }
221410
+ throw new Error(`OAuth2 token request failed: HTTP ${response.status} ${response.statusText}${errorDetail ? ` - ${errorDetail.substring(0, 200)}` : ''}`);
221411
+ }
221412
+ const data = (await response.json());
221413
+ if (!data.access_token) {
221414
+ throw new Error('OAuth2 token response missing access_token');
221415
+ }
221416
+ // Default to 1 hour if expires_in not provided
221417
+ const expiresIn = data.expires_in ?? 3600;
221418
+ const expiresAt = Date.now() + expiresIn * 1000;
221419
+ const cacheKey = `${tokenUrl}|${clientId}`;
221420
+ this.cache.set(cacheKey, {
221421
+ access_token: data.access_token,
221422
+ expires_at: expiresAt,
221423
+ });
221424
+ logger_1.logger.verbose(`[oauth2] Token acquired, expires in ${expiresIn}s`);
221425
+ return data.access_token;
221426
+ }
221427
+ /** Clear all cached tokens (for testing or credential rotation) */
221428
+ clear() {
221429
+ this.cache.clear();
221430
+ }
221431
+ }
221432
+ exports.OAuth2TokenCache = OAuth2TokenCache;
221433
+
221434
+
221191
221435
  /***/ }),
221192
221436
 
221193
221437
  /***/ 12630:
@@ -248108,9 +248352,7 @@ async function acquireFileLock(lockPath, version2) {
248108
248352
  };
248109
248353
  try {
248110
248354
  await import_fs_extra2.default.writeFile(lockPath, JSON.stringify(lockData), { flag: "wx" });
248111
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
248112
- console.log(`Acquired file lock: ${lockPath}`);
248113
- }
248355
+ console.log(`Acquired file lock: ${lockPath}`);
248114
248356
  return true;
248115
248357
  } catch (error2) {
248116
248358
  if (error2.code === "EEXIST") {
@@ -248118,15 +248360,11 @@ async function acquireFileLock(lockPath, version2) {
248118
248360
  const existingLock = JSON.parse(await import_fs_extra2.default.readFile(lockPath, "utf-8"));
248119
248361
  const lockAge = Date.now() - existingLock.timestamp;
248120
248362
  if (lockAge > LOCK_TIMEOUT_MS) {
248121
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
248122
- console.log(`Removing stale lock file (age: ${Math.round(lockAge / 1e3)}s, pid: ${existingLock.pid})`);
248123
- }
248363
+ console.log(`Removing stale lock file (age: ${Math.round(lockAge / 1e3)}s, pid: ${existingLock.pid})`);
248124
248364
  await import_fs_extra2.default.remove(lockPath);
248125
248365
  return false;
248126
248366
  }
248127
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
248128
- console.log(`Download in progress by process ${existingLock.pid}, waiting...`);
248129
- }
248367
+ console.log(`Download in progress by process ${existingLock.pid}, waiting...`);
248130
248368
  return false;
248131
248369
  } catch (readError) {
248132
248370
  if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
@@ -248167,36 +248405,36 @@ async function releaseFileLock(lockPath) {
248167
248405
  }
248168
248406
  async function waitForFileLock(lockPath, binaryPath) {
248169
248407
  const startTime = Date.now();
248408
+ let lastStatusTime = startTime;
248409
+ console.log(`Waiting for file lock to clear: ${lockPath}`);
248170
248410
  while (Date.now() - startTime < MAX_LOCK_WAIT_MS) {
248171
248411
  if (await import_fs_extra2.default.pathExists(binaryPath)) {
248172
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
248173
- console.log(`Binary now available at ${binaryPath}, download completed by another process`);
248174
- }
248412
+ const waitedSeconds = Math.round((Date.now() - startTime) / 1e3);
248413
+ console.log(`Binary now available at ${binaryPath}, download completed by another process (waited ${waitedSeconds}s)`);
248175
248414
  return true;
248176
248415
  }
248177
248416
  const lockExists = await import_fs_extra2.default.pathExists(lockPath);
248178
248417
  if (!lockExists) {
248179
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
248180
- console.log(`Lock file removed but binary not found - download may have failed`);
248181
- }
248418
+ console.log(`Lock file removed but binary not found - download may have failed`);
248182
248419
  return false;
248183
248420
  }
248184
248421
  try {
248185
248422
  const lockData = JSON.parse(await import_fs_extra2.default.readFile(lockPath, "utf-8"));
248186
248423
  const lockAge = Date.now() - lockData.timestamp;
248187
248424
  if (lockAge > LOCK_TIMEOUT_MS) {
248188
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
248189
- console.log(`Lock expired (age: ${Math.round(lockAge / 1e3)}s), will retry download`);
248190
- }
248425
+ console.log(`Lock expired (age: ${Math.round(lockAge / 1e3)}s), will retry download`);
248191
248426
  return false;
248192
248427
  }
248193
248428
  } catch {
248194
248429
  }
248430
+ if (Date.now() - lastStatusTime >= 15e3) {
248431
+ const elapsedSeconds = Math.round((Date.now() - startTime) / 1e3);
248432
+ console.log(`Still waiting for file lock (${elapsedSeconds}s/${MAX_LOCK_WAIT_MS / 1e3}s max)`);
248433
+ lastStatusTime = Date.now();
248434
+ }
248195
248435
  await new Promise((resolve8) => setTimeout(resolve8, LOCK_POLL_INTERVAL_MS));
248196
248436
  }
248197
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
248198
- console.log(`Timeout waiting for file lock`);
248199
- }
248437
+ console.log(`Timeout waiting for file lock after ${MAX_LOCK_WAIT_MS / 1e3}s`);
248200
248438
  return false;
248201
248439
  }
248202
248440
  async function withDownloadLock(version2, downloadFn) {
@@ -248210,9 +248448,7 @@ async function withDownloadLock(version2, downloadFn) {
248210
248448
  }
248211
248449
  downloadLocks.delete(lockKey);
248212
248450
  } else {
248213
- if (process.env.DEBUG === "1" || process.env.VERBOSE === "1") {
248214
- console.log(`Download already in progress in this process for version ${lockKey}, waiting...`);
248215
- }
248451
+ console.log(`Download already in progress in this process for version ${lockKey}, waiting...`);
248216
248452
  try {
248217
248453
  return await lock.promise;
248218
248454
  } catch (error2) {
@@ -248222,10 +248458,16 @@ async function withDownloadLock(version2, downloadFn) {
248222
248458
  }
248223
248459
  }
248224
248460
  }
248461
+ let timeoutId = null;
248225
248462
  const downloadPromise = Promise.race([
248226
248463
  downloadFn(),
248227
248464
  new Promise(
248228
- (_, reject2) => setTimeout(() => reject2(new Error(`Download timeout after ${LOCK_TIMEOUT_MS / 1e3}s`)), LOCK_TIMEOUT_MS)
248465
+ (_, reject2) => {
248466
+ timeoutId = setTimeout(() => reject2(new Error(`Download timeout after ${LOCK_TIMEOUT_MS / 1e3}s`)), LOCK_TIMEOUT_MS);
248467
+ if (timeoutId.unref) {
248468
+ timeoutId.unref();
248469
+ }
248470
+ }
248229
248471
  )
248230
248472
  ]);
248231
248473
  downloadLocks.set(lockKey, {
@@ -248236,6 +248478,9 @@ async function withDownloadLock(version2, downloadFn) {
248236
248478
  const result = await downloadPromise;
248237
248479
  return result;
248238
248480
  } finally {
248481
+ if (timeoutId) {
248482
+ clearTimeout(timeoutId);
248483
+ }
248239
248484
  downloadLocks.delete(lockKey);
248240
248485
  }
248241
248486
  }
@@ -283766,14 +284011,14 @@ function resolveTargetPath(target, cwd) {
283766
284011
  }
283767
284012
  return filePart + suffix;
283768
284013
  }
283769
- var import_path5, searchSchema, searchAllSchema, querySchema, extractSchema, delegateSchema, listSkillsSchema, useSkillSchema, listFilesSchema, searchFilesSchema, readImageSchema, bashSchema, analyzeAllSchema, executePlanSchema, cleanupExecutePlanSchema, attemptCompletionSchema, searchDescription, queryDescription, extractDescription, delegateDescription, bashDescription, analyzeAllDescription;
284014
+ 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;
283770
284015
  var init_common2 = __esm({
283771
284016
  "src/tools/common.js"() {
283772
284017
  "use strict";
283773
284018
  init_zod();
283774
284019
  import_path5 = __nccwpck_require__(16928);
283775
284020
  searchSchema = external_exports.object({
283776
- query: external_exports.string().describe("Search query with Elasticsearch syntax. Use quotes for exact matches, AND/OR for boolean logic, - for negation."),
284021
+ 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."),
283777
284022
  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.'),
283778
284023
  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.'),
283779
284024
  maxTokens: external_exports.number().nullable().optional().describe("Maximum tokens to return. Default is 20000. Set to null for unlimited results."),
@@ -283781,7 +284026,7 @@ var init_common2 = __esm({
283781
284026
  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.")
283782
284027
  });
283783
284028
  searchAllSchema = external_exports.object({
283784
- query: external_exports.string().describe("Search query with Elasticsearch syntax. Use quotes for exact matches, AND/OR for boolean logic, - for negation."),
284029
+ 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."),
283785
284030
  path: external_exports.string().optional().default(".").describe("Path to search in."),
283786
284031
  exact: external_exports.boolean().optional().default(false).describe("Use exact matching instead of stemming."),
283787
284032
  maxTokensPerPage: external_exports.number().optional().default(2e4).describe("Tokens per page when paginating. Default 20000."),
@@ -283888,7 +284133,8 @@ var init_common2 = __esm({
283888
284133
  };
283889
284134
  }
283890
284135
  };
283891
- 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.";
284136
+ 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.';
284137
+ 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.';
283892
284138
  queryDescription = "Search code using ast-grep structural pattern matching. Use this tool to find specific code structures like functions, classes, or methods.";
283893
284139
  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.";
283894
284140
  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.";
@@ -349208,6 +349454,7 @@ function generateSandboxGlobals(options) {
349208
349454
  executing.add(p5);
349209
349455
  results.push(p5);
349210
349456
  if (executing.size >= mapConcurrency) {
349457
+ console.error(`[map] Concurrency limit reached (${executing.size}/${mapConcurrency}), waiting for a slot...`);
349211
349458
  await Promise.race(executing);
349212
349459
  }
349213
349460
  }
@@ -353437,8 +353684,23 @@ __export(ProbeAgent_exports, {
353437
353684
  ENGINE_ACTIVITY_TIMEOUT_DEFAULT: () => ENGINE_ACTIVITY_TIMEOUT_DEFAULT,
353438
353685
  ENGINE_ACTIVITY_TIMEOUT_MAX: () => ENGINE_ACTIVITY_TIMEOUT_MAX,
353439
353686
  ENGINE_ACTIVITY_TIMEOUT_MIN: () => ENGINE_ACTIVITY_TIMEOUT_MIN,
353440
- ProbeAgent: () => ProbeAgent
353687
+ ProbeAgent: () => ProbeAgent,
353688
+ debugLogToolResults: () => debugLogToolResults,
353689
+ debugTruncate: () => debugTruncate
353441
353690
  });
353691
+ function debugTruncate(s5, limit = 200) {
353692
+ if (s5.length <= limit) return s5;
353693
+ const half = Math.floor(limit / 2);
353694
+ return s5.substring(0, half) + ` ... [${s5.length} chars] ... ` + s5.substring(s5.length - half);
353695
+ }
353696
+ function debugLogToolResults(toolResults) {
353697
+ if (!toolResults || toolResults.length === 0) return;
353698
+ for (const tr of toolResults) {
353699
+ const argsStr = JSON.stringify(tr.args || {});
353700
+ const resultStr = typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result || "");
353701
+ console.log(`[DEBUG] tool: ${tr.toolName} | args: ${debugTruncate(argsStr)} | result: ${debugTruncate(resultStr)}`);
353702
+ }
353703
+ }
353442
353704
  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;
353443
353705
  var init_ProbeAgent = __esm({
353444
353706
  "src/agent/ProbeAgent.js"() {
@@ -353558,6 +353820,7 @@ var init_ProbeAgent = __esm({
353558
353820
  this.enableExecutePlan = !!options.enableExecutePlan;
353559
353821
  this.debug = options.debug || process.env.DEBUG === "1";
353560
353822
  this.cancelled = false;
353823
+ this._abortController = new AbortController();
353561
353824
  this.tracer = options.tracer || null;
353562
353825
  this.outline = !!options.outline;
353563
353826
  this.searchDelegate = options.searchDelegate !== void 0 ? !!options.searchDelegate : true;
@@ -353586,6 +353849,7 @@ var init_ProbeAgent = __esm({
353586
353849
  this.completionPrompt = options.completionPrompt || null;
353587
353850
  this.thinkingEffort = options.thinkingEffort || null;
353588
353851
  const effectiveAllowedTools = options.disableTools ? [] : options.allowedTools;
353852
+ this._rawAllowedTools = options.allowedTools;
353589
353853
  this.allowedTools = this._parseAllowedTools(effectiveAllowedTools);
353590
353854
  this.storageAdapter = options.storageAdapter || new InMemoryStorageAdapter();
353591
353855
  this.hooks = new HookManager();
@@ -353728,6 +353992,16 @@ var init_ProbeAgent = __esm({
353728
353992
  _filterMcpTools(mcpToolNames) {
353729
353993
  return mcpToolNames.filter((toolName) => this._isMcpToolAllowed(toolName));
353730
353994
  }
353995
+ /**
353996
+ * Check if query tool was explicitly listed in allowedTools (not via wildcard).
353997
+ * Query (ast-grep) is excluded by default because models struggle with AST pattern syntax.
353998
+ * @returns {boolean}
353999
+ * @private
354000
+ */
354001
+ _isQueryExplicitlyAllowed() {
354002
+ if (!this._rawAllowedTools) return false;
354003
+ return Array.isArray(this._rawAllowedTools) && this._rawAllowedTools.includes("query");
354004
+ }
353731
354005
  /**
353732
354006
  * Check if tracer is AppTracer (expects sessionId as first param) vs SimpleAppTracer
353733
354007
  * @returns {boolean} - True if tracer is AppTracer style (requires sessionId)
@@ -354003,6 +354277,8 @@ var init_ProbeAgent = __esm({
354003
354277
  searchDelegateModel: this.searchDelegateModel,
354004
354278
  delegationManager: this.delegationManager,
354005
354279
  // Per-instance delegation limits
354280
+ parentAbortSignal: this._abortController.signal,
354281
+ // Propagate cancellation to delegations
354006
354282
  outputBuffer: this._outputBuffer,
354007
354283
  concurrencyLimiter: this.concurrencyLimiter,
354008
354284
  // Global AI concurrency limiter
@@ -354019,7 +354295,7 @@ var init_ProbeAgent = __esm({
354019
354295
  if (wrappedTools.searchToolInstance && isToolAllowed("search")) {
354020
354296
  this.toolImplementations.search = wrappedTools.searchToolInstance;
354021
354297
  }
354022
- if (wrappedTools.queryToolInstance && isToolAllowed("query")) {
354298
+ if (wrappedTools.queryToolInstance && isToolAllowed("query") && this._isQueryExplicitlyAllowed()) {
354023
354299
  this.toolImplementations.query = wrappedTools.queryToolInstance;
354024
354300
  }
354025
354301
  if (wrappedTools.extractToolInstance && isToolAllowed("extract")) {
@@ -354450,6 +354726,15 @@ var init_ProbeAgent = __esm({
354450
354726
  }
354451
354727
  const controller = new AbortController();
354452
354728
  const timeoutState = { timeoutId: null };
354729
+ if (this._abortController.signal.aborted) {
354730
+ controller.abort();
354731
+ } else {
354732
+ const onAgentAbort = () => controller.abort();
354733
+ this._abortController.signal.addEventListener("abort", onAgentAbort, { once: true });
354734
+ controller.signal.addEventListener("abort", () => {
354735
+ this._abortController.signal.removeEventListener("abort", onAgentAbort);
354736
+ }, { once: true });
354737
+ }
354453
354738
  if (this.maxOperationTimeout && this.maxOperationTimeout > 0) {
354454
354739
  timeoutState.timeoutId = setTimeout(() => {
354455
354740
  controller.abort();
@@ -354753,7 +355038,8 @@ var init_ProbeAgent = __esm({
354753
355038
  allowEdit: this.allowEdit,
354754
355039
  allowedTools: allowedToolsForDelegate,
354755
355040
  debug: this.debug,
354756
- tracer: this.tracer
355041
+ tracer: this.tracer,
355042
+ parentAbortSignal: this._abortController.signal
354757
355043
  };
354758
355044
  if (this.debug) {
354759
355045
  console.log(`[DEBUG] Executing delegate tool`);
@@ -354948,12 +355234,13 @@ var init_ProbeAgent = __esm({
354948
355234
  const toolMap = {
354949
355235
  search: {
354950
355236
  schema: searchSchema,
354951
- description: "Search code in the repository using keyword queries with Elasticsearch syntax."
354952
- },
354953
- query: {
354954
- schema: querySchema,
354955
- description: "Search code using ast-grep structural pattern matching."
355237
+ 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."
354956
355238
  },
355239
+ // query tool (ast-grep) removed from AI-facing tools — models struggle with pattern syntax
355240
+ // query: {
355241
+ // schema: querySchema,
355242
+ // description: 'Search code using ast-grep structural pattern matching.'
355243
+ // },
354957
355244
  extract: {
354958
355245
  schema: extractSchema,
354959
355246
  description: "Extract code blocks from files based on file paths and optional line numbers."
@@ -355621,25 +355908,27 @@ ${this.architectureContext.content}
355621
355908
  } else {
355622
355909
  systemPrompt += predefinedPrompts["code-explorer"] + "\n\n";
355623
355910
  }
355911
+ 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.";
355624
355912
  systemPrompt += `You have access to powerful code search and analysis tools through MCP:
355625
- - search: Find code patterns using semantic search
355913
+ ${searchToolDesc1}
355626
355914
  - extract: Extract specific code sections with context
355627
- - query: Use AST patterns for structural code matching
355628
355915
  - listFiles: Browse directory contents
355629
355916
  - searchFiles: Find files by name patterns`;
355630
355917
  if (this.enableBash) {
355631
355918
  systemPrompt += `
355632
355919
  - bash: Execute bash commands for system operations`;
355633
355920
  }
355634
- const searchGuidance = this.searchDelegate ? "1. Start with search to retrieve extracted code blocks" : "1. Start with search to find relevant code patterns";
355635
- 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";
355921
+ 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.";
355922
+ 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";
355636
355923
  systemPrompt += `
355637
355924
 
355638
355925
  When exploring code:
355639
- ${searchGuidance}
355640
- ${extractGuidance}
355926
+ ${searchGuidance1}
355927
+ ${extractGuidance1}
355641
355928
  3. Prefer focused, specific searches over broad queries
355642
- 4. Combine multiple tools to build complete understanding`;
355929
+ 4. Do NOT repeat the same search or try trivial keyword variations \u2014 probe handles stemming and case variations automatically
355930
+ 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
355931
+ 6. Combine multiple tools to build complete understanding`;
355643
355932
  if (this.allowedFolders && this.allowedFolders.length > 0) {
355644
355933
  systemPrompt += `
355645
355934
 
@@ -355674,25 +355963,27 @@ Workspace: ${this.allowedFolders.join(", ")}`;
355674
355963
  } else {
355675
355964
  systemPrompt += predefinedPrompts["code-explorer"] + "\n\n";
355676
355965
  }
355966
+ 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.";
355677
355967
  systemPrompt += `You have access to powerful code search and analysis tools through MCP:
355678
- - search: Find code patterns using semantic search
355968
+ ${searchToolDesc2}
355679
355969
  - extract: Extract specific code sections with context
355680
- - query: Use AST patterns for structural code matching
355681
355970
  - listFiles: Browse directory contents
355682
355971
  - searchFiles: Find files by name patterns`;
355683
355972
  if (this.enableBash) {
355684
355973
  systemPrompt += `
355685
355974
  - bash: Execute bash commands for system operations`;
355686
355975
  }
355687
- const searchGuidance = this.searchDelegate ? "1. Start with search to retrieve extracted code blocks" : "1. Start with search to find relevant code patterns";
355688
- 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";
355976
+ 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.";
355977
+ 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";
355689
355978
  systemPrompt += `
355690
355979
 
355691
355980
  When exploring code:
355692
- ${searchGuidance}
355693
- ${extractGuidance}
355981
+ ${searchGuidance2}
355982
+ ${extractGuidance2}
355694
355983
  3. Prefer focused, specific searches over broad queries
355695
- 4. Combine multiple tools to build complete understanding`;
355984
+ 4. Do NOT repeat the same search or try trivial keyword variations \u2014 probe handles stemming and case variations automatically
355985
+ 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
355986
+ 6. Combine multiple tools to build complete understanding`;
355696
355987
  if (this.allowedFolders && this.allowedFolders.length > 0) {
355697
355988
  systemPrompt += `
355698
355989
 
@@ -355743,10 +356034,10 @@ Workspace: ${this.allowedFolders.join(", ")}`;
355743
356034
  Follow these instructions carefully:
355744
356035
  1. Analyze the user's request.
355745
356036
  2. Use the available tools step-by-step to fulfill the request.
355746
- 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."}
356037
+ 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."}
355747
356038
  4. Ensure to get really deep and understand the full picture before answering.
355748
356039
  5. Once the task is fully completed, use the attempt_completion tool to provide the final result.
355749
- 6. Prefer concise and focused search queries. Use specific keywords and phrases to narrow down results.${this.allowEdit ? `
356040
+ 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 ? `
355750
356041
  7. When modifying files, choose the appropriate tool:
355751
356042
  - Use 'edit' for all code modifications:
355752
356043
  * 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.
@@ -356070,6 +356361,10 @@ You are working with a workspace. Available paths: ${workspaceDesc}
356070
356361
  completionResult = result;
356071
356362
  completionAttempted = true;
356072
356363
  }, toolContext);
356364
+ if (this.debug) {
356365
+ const toolNames = Object.keys(tools2);
356366
+ console.log(`[DEBUG] Agent tools registered (${toolNames.length}): ${toolNames.join(", ")}`);
356367
+ }
356073
356368
  let maxResponseTokens = this.maxResponseTokens;
356074
356369
  if (!maxResponseTokens) {
356075
356370
  maxResponseTokens = 4e3;
@@ -356109,6 +356404,7 @@ You are working with a workspace. Available paths: ${workspaceDesc}
356109
356404
  }
356110
356405
  if (this.debug) {
356111
356406
  console.log(`[DEBUG] Step ${currentIteration}/${maxIterations} finished (reason: ${finishReason}, tools: ${toolResults?.length || 0})`);
356407
+ debugLogToolResults(toolResults);
356112
356408
  }
356113
356409
  }
356114
356410
  };
@@ -356265,6 +356561,7 @@ Double-check your response based on the criteria above. If everything looks good
356265
356561
  }
356266
356562
  if (this.debug) {
356267
356563
  console.log(`[DEBUG] Completion prompt step finished (reason: ${finishReason}, tools: ${toolResults?.length || 0})`);
356564
+ debugLogToolResults(toolResults);
356268
356565
  }
356269
356566
  }
356270
356567
  };
@@ -356975,6 +357272,9 @@ Convert your previous response content into actual JSON data that follows this s
356975
357272
  * Clean up resources (including MCP connections)
356976
357273
  */
356977
357274
  async cleanup() {
357275
+ if (!this._abortController.signal.aborted) {
357276
+ this._abortController.abort();
357277
+ }
356978
357278
  if (this.mcpBridge) {
356979
357279
  try {
356980
357280
  await this.mcpBridge.cleanup();
@@ -356998,14 +357298,25 @@ Convert your previous response content into actual JSON data that follows this s
356998
357298
  this.clearHistory();
356999
357299
  }
357000
357300
  /**
357001
- * Cancel the current request
357301
+ * Cancel the current request and all in-flight delegations.
357302
+ * Aborts the internal AbortController so streamText, subagents,
357303
+ * and any code checking the signal will stop.
357002
357304
  */
357003
357305
  cancel() {
357004
357306
  this.cancelled = true;
357307
+ this._abortController.abort();
357005
357308
  if (this.debug) {
357006
357309
  console.log(`[DEBUG] Agent cancelled for session ${this.sessionId}`);
357007
357310
  }
357008
357311
  }
357312
+ /**
357313
+ * Get the abort signal for this agent.
357314
+ * Delegations and subagents should check this signal.
357315
+ * @returns {AbortSignal}
357316
+ */
357317
+ get abortSignal() {
357318
+ return this._abortController.signal;
357319
+ }
357009
357320
  };
357010
357321
  }
357011
357322
  });
@@ -357038,12 +357349,17 @@ async function delegate({
357038
357349
  mcpConfigPath = null,
357039
357350
  delegationManager = null,
357040
357351
  // Optional per-instance manager, falls back to default singleton
357041
- concurrencyLimiter = null
357352
+ concurrencyLimiter = null,
357042
357353
  // Optional global AI concurrency limiter
357354
+ parentAbortSignal = null
357355
+ // Optional AbortSignal from parent to cancel this delegation
357043
357356
  }) {
357044
357357
  if (!task || typeof task !== "string") {
357045
357358
  throw new Error("Task parameter is required and must be a string");
357046
357359
  }
357360
+ if (parentAbortSignal?.aborted) {
357361
+ throw new Error("Delegation cancelled: parent operation was aborted");
357362
+ }
357047
357363
  const hasExplicitTimeout = Object.prototype.hasOwnProperty.call(arguments?.[0] ?? {}, "timeout");
357048
357364
  if (!hasExplicitTimeout) {
357049
357365
  const envTimeoutMs = parseInt(process.env.DELEGATION_TIMEOUT_MS || "", 10);
@@ -357128,12 +357444,37 @@ async function delegate({
357128
357444
  }
357129
357445
  const timeoutPromise = new Promise((_, reject2) => {
357130
357446
  timeoutId = setTimeout(() => {
357447
+ subagent.cancel();
357131
357448
  reject2(new Error(`Delegation timed out after ${timeout} seconds`));
357132
357449
  }, timeout * 1e3);
357133
357450
  });
357451
+ let parentAbortHandler;
357452
+ const parentAbortPromise = new Promise((_, reject2) => {
357453
+ if (parentAbortSignal) {
357454
+ if (parentAbortSignal.aborted) {
357455
+ subagent.cancel();
357456
+ reject2(new Error("Delegation cancelled: parent operation was aborted"));
357457
+ return;
357458
+ }
357459
+ parentAbortHandler = () => {
357460
+ subagent.cancel();
357461
+ reject2(new Error("Delegation cancelled: parent operation was aborted"));
357462
+ };
357463
+ parentAbortSignal.addEventListener("abort", parentAbortHandler, { once: true });
357464
+ }
357465
+ });
357134
357466
  const answerOptions = schema ? { schema } : void 0;
357135
357467
  const answerPromise = answerOptions ? subagent.answer(task, [], answerOptions) : subagent.answer(task);
357136
- const response = await Promise.race([answerPromise, timeoutPromise]);
357468
+ const racers = [answerPromise, timeoutPromise];
357469
+ if (parentAbortSignal) racers.push(parentAbortPromise);
357470
+ let response;
357471
+ try {
357472
+ response = await Promise.race(racers);
357473
+ } finally {
357474
+ if (parentAbortHandler && parentAbortSignal) {
357475
+ parentAbortSignal.removeEventListener("abort", parentAbortHandler);
357476
+ }
357477
+ }
357137
357478
  if (timeoutId !== null) {
357138
357479
  clearTimeout(timeoutId);
357139
357480
  timeoutId = null;
@@ -357289,10 +357630,9 @@ var init_delegate = __esm({
357289
357630
  if (this.tryAcquire(parentSessionId)) {
357290
357631
  return true;
357291
357632
  }
357292
- if (debug) {
357293
- console.error(`[DelegationManager] Slot unavailable (${this.globalActive}/${this.maxConcurrent}), queuing... (queue size: ${this.waitQueue.length}, timeout: ${effectiveTimeout}ms)`);
357294
- }
357633
+ console.error(`[DelegationManager] Slot unavailable (${this.globalActive}/${this.maxConcurrent}), queuing... (queue size: ${this.waitQueue.length + 1}, timeout: ${effectiveTimeout}ms)`);
357295
357634
  return new Promise((resolve8, reject2) => {
357635
+ const queuedAt = Date.now();
357296
357636
  const entry = {
357297
357637
  resolve: null,
357298
357638
  // Will be wrapped below
@@ -357300,20 +357640,23 @@ var init_delegate = __esm({
357300
357640
  // Will be wrapped below
357301
357641
  parentSessionId,
357302
357642
  debug,
357303
- queuedAt: Date.now(),
357304
- timeoutId: null
357643
+ queuedAt,
357644
+ timeoutId: null,
357645
+ reminderId: null
357305
357646
  };
357306
357647
  let settled = false;
357307
357648
  entry.resolve = (value) => {
357308
357649
  if (settled) return;
357309
357650
  settled = true;
357310
357651
  if (entry.timeoutId) clearTimeout(entry.timeoutId);
357652
+ if (entry.reminderId) clearInterval(entry.reminderId);
357311
357653
  resolve8(value);
357312
357654
  };
357313
357655
  entry.reject = (error2) => {
357314
357656
  if (settled) return;
357315
357657
  settled = true;
357316
357658
  if (entry.timeoutId) clearTimeout(entry.timeoutId);
357659
+ if (entry.reminderId) clearInterval(entry.reminderId);
357317
357660
  reject2(error2);
357318
357661
  };
357319
357662
  if (effectiveTimeout > 0) {
@@ -357325,6 +357668,13 @@ var init_delegate = __esm({
357325
357668
  entry.reject(new Error(`Delegation queue timeout: waited ${effectiveTimeout}ms for an available slot`));
357326
357669
  }, effectiveTimeout);
357327
357670
  }
357671
+ entry.reminderId = setInterval(() => {
357672
+ const waitedSeconds = Math.round((Date.now() - queuedAt) / 1e3);
357673
+ console.error(`[DelegationManager] Still waiting for slot (${waitedSeconds}s). ${this.globalActive}/${this.maxConcurrent} active, ${this.waitQueue.length} queued.`);
357674
+ }, 15e3);
357675
+ if (entry.reminderId.unref) {
357676
+ entry.reminderId.unref();
357677
+ }
357328
357678
  this.waitQueue.push(entry);
357329
357679
  });
357330
357680
  }
@@ -357363,18 +357713,14 @@ var init_delegate = __esm({
357363
357713
  const sessionData = this.sessionDelegations.get(parentSessionId);
357364
357714
  const sessionCount = sessionData?.count || 0;
357365
357715
  if (sessionCount >= this.maxPerSession) {
357366
- if (debug) {
357367
- console.error(`[DelegationManager] Session limit (${this.maxPerSession}) reached for queued item, rejecting`);
357368
- }
357716
+ console.error(`[DelegationManager] Session limit (${this.maxPerSession}) reached for queued item, rejecting`);
357369
357717
  toReject.push({ reject: reject2, error: new Error(`Maximum delegations per session (${this.maxPerSession}) reached for session ${parentSessionId}`) });
357370
357718
  continue;
357371
357719
  }
357372
357720
  }
357373
357721
  this._incrementCounters(parentSessionId);
357374
- if (debug) {
357375
- const waitTime = Date.now() - queuedAt;
357376
- console.error(`[DelegationManager] Granted slot from queue (waited ${waitTime}ms). Active: ${this.globalActive}/${this.maxConcurrent}`);
357377
- }
357722
+ const waitTime = Date.now() - queuedAt;
357723
+ console.error(`[DelegationManager] Granted slot from queue (waited ${waitTime}ms). Active: ${this.globalActive}/${this.maxConcurrent}`);
357378
357724
  toResolve.push(resolve8);
357379
357725
  }
357380
357726
  if (toResolve.length > 0 || toReject.length > 0) {
@@ -357424,6 +357770,9 @@ var init_delegate = __esm({
357424
357770
  if (entry.timeoutId) {
357425
357771
  clearTimeout(entry.timeoutId);
357426
357772
  }
357773
+ if (entry.reminderId) {
357774
+ clearInterval(entry.reminderId);
357775
+ }
357427
357776
  if (entry.reject) {
357428
357777
  entry.reject(new Error("DelegationManager was cleaned up"));
357429
357778
  }
@@ -357546,8 +357895,9 @@ Instructions:
357546
357895
  promptType: "code-researcher",
357547
357896
  allowedTools: ["extract"],
357548
357897
  maxIterations: 5,
357549
- delegationManager: options.delegationManager
357898
+ delegationManager: options.delegationManager,
357550
357899
  // Per-instance delegation limits
357900
+ parentAbortSignal: options.parentAbortSignal || null
357551
357901
  // timeout removed - inherit default from delegate (300s)
357552
357902
  });
357553
357903
  return { chunk, result };
@@ -357567,16 +357917,12 @@ async function processChunksParallel(chunks, extractionPrompt, maxWorkers, optio
357567
357917
  return result;
357568
357918
  });
357569
357919
  active.add(promise);
357570
- if (options.debug) {
357571
- console.error(`[analyze_all] Started processing chunk ${chunk.id}/${chunk.total}`);
357572
- }
357920
+ console.error(`[analyze_all] Started processing chunk ${chunk.id}/${chunk.total}`);
357573
357921
  }
357574
357922
  if (active.size > 0) {
357575
357923
  const result = await Promise.race(active);
357576
357924
  results.push(result);
357577
- if (options.debug) {
357578
- console.error(`[analyze_all] Completed chunk ${result.chunk.id}/${result.chunk.total}`);
357579
- }
357925
+ console.error(`[analyze_all] Completed chunk ${result.chunk.id}/${result.chunk.total}`);
357580
357926
  }
357581
357927
  }
357582
357928
  results.sort((a5, b5) => a5.chunk.id - b5.chunk.id);
@@ -357646,8 +357992,9 @@ Organize all findings into clear categories with items listed under each.${compl
357646
357992
  promptType: "code-researcher",
357647
357993
  allowedTools: [],
357648
357994
  maxIterations: 5,
357649
- delegationManager: options.delegationManager
357995
+ delegationManager: options.delegationManager,
357650
357996
  // Per-instance delegation limits
357997
+ parentAbortSignal: options.parentAbortSignal || null
357651
357998
  // timeout removed - inherit default from delegate (300s)
357652
357999
  });
357653
358000
  return result;
@@ -357711,8 +358058,9 @@ CRITICAL: Do NOT guess keywords. Actually run searches and see what returns resu
357711
358058
  promptType: "code-researcher",
357712
358059
  // Full tool access for exploration and experimentation
357713
358060
  maxIterations: 15,
357714
- delegationManager: options.delegationManager
358061
+ delegationManager: options.delegationManager,
357715
358062
  // Per-instance delegation limits
358063
+ parentAbortSignal: options.parentAbortSignal || null
357716
358064
  // timeout removed - inherit default from delegate (300s)
357717
358065
  });
357718
358066
  const plan = parsePlanningResult(stripResultTags(result));
@@ -357769,8 +358117,9 @@ When done, use the attempt_completion tool with your answer as the result.`;
357769
358117
  promptType: "code-researcher",
357770
358118
  allowedTools: [],
357771
358119
  maxIterations: 5,
357772
- delegationManager: options.delegationManager
358120
+ delegationManager: options.delegationManager,
357773
358121
  // Per-instance delegation limits
358122
+ parentAbortSignal: options.parentAbortSignal || null
357774
358123
  // timeout removed - inherit default from delegate (300s)
357775
358124
  });
357776
358125
  return stripResultTags(result);
@@ -358083,11 +358432,41 @@ function buildSearchDelegateTask({ searchQuery, searchPath, exact, language, all
358083
358432
  "- extract: Verify code snippets to ensure targets are actually relevant before including them.",
358084
358433
  "- listFiles: Understand directory structure to find where relevant code might live.",
358085
358434
  "",
358086
- "Strategy for complex queries:",
358435
+ "CRITICAL - How probe search works (do NOT ignore):",
358436
+ "- By default (exact=false), probe ALREADY handles stemming, case-insensitive matching, and camelCase/snake_case splitting.",
358437
+ '- Searching "allowed_ips" ALREADY matches "AllowedIPs", "allowedIps", "allowed_ips", etc. Do NOT manually try case/style variations.',
358438
+ '- Searching "getUserData" ALREADY matches "get", "user", "data" and their variations.',
358439
+ "- NEVER repeat the same search query \u2014 you will get the same results.",
358440
+ "- NEVER search trivial variations of the same keyword (e.g., AllowedIPs then allowedIps then allowed_ips). This is wasteful \u2014 probe handles it.",
358441
+ "- 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.",
358442
+ "- If 2-3 consecutive searches return no results for a concept, STOP searching for it and move on.",
358443
+ "",
358444
+ "GOOD search strategy (do this):",
358445
+ ' Query: "How does authentication work and how are sessions managed?"',
358446
+ ' \u2192 search "authentication" \u2192 search "session management" (two different concepts)',
358447
+ ' Query: "Find the IP allowlist middleware"',
358448
+ ' \u2192 search "allowlist middleware" (one search, probe handles IP/ip/Ip variations)',
358449
+ ' Query: "How does BM25 scoring work with SIMD optimization?"',
358450
+ ' \u2192 search "BM25 scoring" \u2192 search "SIMD optimization" (two different concepts)',
358451
+ "",
358452
+ "BAD search strategy (never do this):",
358453
+ ' \u2192 search "AllowedIPs" \u2192 search "allowedIps" \u2192 search "allowed_ips" (WRONG: these are trivial case variations, probe handles them)',
358454
+ ' \u2192 search "CIDR" \u2192 search "cidr" \u2192 search "Cidr" \u2192 search "*cidr*" (WRONG: same keyword repeated with variations)',
358455
+ ' \u2192 search "error handling" \u2192 search "error handling" \u2192 search "error handling" (WRONG: repeating exact same query)',
358456
+ "",
358457
+ "Keyword tips:",
358458
+ "- Common programming keywords are filtered as stopwords when unquoted: function, class, return, new, struct, impl, var, let, const, etc.",
358459
+ '- Avoid searching for these alone \u2014 combine with a specific term (e.g., "middleware function" is fine, "function" alone is too generic).',
358460
+ '- To bypass stopword filtering: wrap terms in quotes ("return", "struct") or set exact=true. Both disable stemming and splitting too.',
358461
+ "- Multiple words without operators use OR logic: foo bar = foo OR bar. Use AND explicitly if you need both: foo AND bar.",
358462
+ '- camelCase terms are split: getUserData becomes "get", "user", "data" \u2014 so one search covers all naming styles.',
358463
+ "",
358464
+ "Strategy:",
358087
358465
  "1. Analyze the query - identify key concepts, entities, and relationships",
358088
- '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)',
358089
- "3. Use extract to verify relevance of promising results",
358090
- "4. Combine all relevant targets in your final response",
358466
+ "2. Run ONE focused search per concept with the most natural keyword. Trust probe to handle variations.",
358467
+ "3. If a search returns results, use extract to verify relevance",
358468
+ "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)",
358469
+ "5. Combine all relevant targets in your final response",
358091
358470
  "",
358092
358471
  `Query: ${searchQuery}`,
358093
358472
  `Search path(s): ${searchPath}`,
@@ -358140,9 +358519,12 @@ var init_vercel = __esm({
358140
358519
  }
358141
358520
  return result;
358142
358521
  };
358522
+ const previousSearches = /* @__PURE__ */ new Set();
358523
+ const paginationCounts = /* @__PURE__ */ new Map();
358524
+ const MAX_PAGES_PER_QUERY = 3;
358143
358525
  return (0, import_ai5.tool)({
358144
358526
  name: "search",
358145
- description: searchDelegate ? `${searchDescription} (delegates code search to a subagent and returns extracted code blocks)` : searchDescription,
358527
+ description: searchDelegate ? searchDelegateDescription : searchDescription,
358146
358528
  inputSchema: searchSchema,
358147
358529
  execute: async ({ query: searchQuery, path: path9, allow_tests, exact, maxTokens: paramMaxTokens, language, session, nextPage }) => {
358148
358530
  const effectiveMaxTokens = paramMaxTokens || maxTokens;
@@ -358180,6 +358562,26 @@ var init_vercel = __esm({
358180
358562
  return await search(searchOptions);
358181
358563
  };
358182
358564
  if (!searchDelegate) {
358565
+ const searchKey = `${searchQuery}::${searchPath}::${exact || false}`;
358566
+ if (!nextPage) {
358567
+ if (previousSearches.has(searchKey)) {
358568
+ if (debug) {
358569
+ console.error(`[DEDUP] Blocked duplicate search: "${searchQuery}" in "${searchPath}"`);
358570
+ }
358571
+ 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.";
358572
+ }
358573
+ previousSearches.add(searchKey);
358574
+ paginationCounts.set(searchKey, 0);
358575
+ } else {
358576
+ const pageCount = (paginationCounts.get(searchKey) || 0) + 1;
358577
+ paginationCounts.set(searchKey, pageCount);
358578
+ if (pageCount > MAX_PAGES_PER_QUERY) {
358579
+ if (debug) {
358580
+ console.error(`[DEDUP] Blocked excessive pagination (page ${pageCount}/${MAX_PAGES_PER_QUERY}): "${searchQuery}" in "${searchPath}"`);
358581
+ }
358582
+ 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.`;
358583
+ }
358584
+ }
358183
358585
  try {
358184
358586
  const result = maybeAnnotate(await runRawSearch());
358185
358587
  if (options.fileTracker && typeof result === "string") {
@@ -358218,7 +358620,8 @@ var init_vercel = __esm({
358218
358620
  promptType: "code-searcher",
358219
358621
  allowedTools: ["search", "extract", "listFiles", "attempt_completion"],
358220
358622
  searchDelegate: false,
358221
- schema: CODE_SEARCH_SCHEMA
358623
+ schema: CODE_SEARCH_SCHEMA,
358624
+ parentAbortSignal: options.parentAbortSignal || null
358222
358625
  });
358223
358626
  const delegateResult = options.tracer?.withSpan ? await options.tracer.withSpan("search.delegate", runDelegation, {
358224
358627
  "search.query": searchQuery,
@@ -358432,7 +358835,7 @@ var init_vercel = __esm({
358432
358835
  name: "delegate",
358433
358836
  description: delegateDescription,
358434
358837
  inputSchema: delegateSchema,
358435
- execute: async ({ task, currentIteration, maxIterations, parentSessionId, path: path9, provider, model, tracer, searchDelegate }) => {
358838
+ execute: async ({ task, currentIteration, maxIterations, parentSessionId, path: path9, provider, model, tracer, searchDelegate, parentAbortSignal }) => {
358436
358839
  if (!task || typeof task !== "string") {
358437
358840
  throw new Error("Task parameter is required and must be a non-empty string");
358438
358841
  }
@@ -358490,8 +358893,9 @@ var init_vercel = __esm({
358490
358893
  enableMcp,
358491
358894
  mcpConfig,
358492
358895
  mcpConfigPath,
358493
- delegationManager
358896
+ delegationManager,
358494
358897
  // Per-instance delegation limits
358898
+ parentAbortSignal
358495
358899
  });
358496
358900
  return result;
358497
358901
  }
@@ -358529,8 +358933,9 @@ var init_vercel = __esm({
358529
358933
  provider: options.provider,
358530
358934
  model: options.model,
358531
358935
  tracer: options.tracer,
358532
- delegationManager
358936
+ delegationManager,
358533
358937
  // Per-instance delegation limits
358938
+ parentAbortSignal: options.parentAbortSignal || null
358534
358939
  });
358535
358940
  return result;
358536
358941
  } catch (error2) {
@@ -395746,7 +396151,7 @@ module.exports = /*#__PURE__*/JSON.parse('{"100":"Continue","101":"Switching Pro
395746
396151
  /***/ ((module) => {
395747
396152
 
395748
396153
  "use strict";
395749
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@probelabs/visor","version":"0.1.165","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"]}}');
396154
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@probelabs/visor","version":"0.1.166","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"]}}');
395750
396155
 
395751
396156
  /***/ })
395752
396157