snow-flow 2.9.0 → 2.9.4

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.
@@ -14,6 +14,7 @@ const events_1 = require("events");
14
14
  const os_1 = __importDefault(require("os"));
15
15
  const unified_auth_store_js_1 = require("./unified-auth-store.js");
16
16
  const mcp_singleton_lock_js_1 = require("./mcp-singleton-lock.js");
17
+ const mcp_process_manager_js_1 = require("./mcp-process-manager.js");
17
18
  class MCPServerManager extends events_1.EventEmitter {
18
19
  constructor(configPath) {
19
20
  super();
@@ -148,6 +149,16 @@ class MCPServerManager extends events_1.EventEmitter {
148
149
  if (server.status === 'running') {
149
150
  return true; // Already running
150
151
  }
152
+ // Check if we can spawn a new server
153
+ const processManager = mcp_process_manager_js_1.MCPProcessManager.getInstance();
154
+ if (!processManager.canSpawnServer()) {
155
+ // Try cleanup first
156
+ processManager.cleanup();
157
+ // Check again after cleanup
158
+ if (!processManager.canSpawnServer()) {
159
+ throw new Error('Cannot spawn server: resource limits exceeded. Too many MCP processes running.');
160
+ }
161
+ }
151
162
  server.status = 'starting';
152
163
  this.emit('serverStarting', name);
153
164
  try {
@@ -252,15 +263,20 @@ class MCPServerManager extends events_1.EventEmitter {
252
263
  try {
253
264
  // Try graceful shutdown first
254
265
  server.process.kill('SIGTERM');
255
- // Wait for graceful shutdown
266
+ // Wait for graceful shutdown with shorter timeout to prevent hanging
256
267
  await new Promise((resolve) => {
257
268
  const timeout = setTimeout(() => {
258
269
  // Force kill if graceful shutdown fails
259
270
  if (server.process) {
260
- server.process.kill('SIGKILL');
271
+ try {
272
+ server.process.kill('SIGKILL');
273
+ }
274
+ catch (e) {
275
+ // Process might already be dead
276
+ }
261
277
  }
262
278
  resolve(undefined);
263
- }, 5000);
279
+ }, 2000); // Reduced from 5000ms to 2000ms to prevent hanging
264
280
  server.process?.on('exit', () => {
265
281
  clearTimeout(timeout);
266
282
  resolve(undefined);
@@ -287,12 +303,31 @@ class MCPServerManager extends events_1.EventEmitter {
287
303
  if (!singletonLock.acquire()) {
288
304
  throw new Error('❌ MCP servers already running. Cannot start duplicate instances.');
289
305
  }
290
- console.log('✅ Starting all MCP servers (singleton protected)...');
291
- const promises = Array.from(this.servers.keys()).map(name => this.startServer(name).catch(error => {
292
- console.error(`Failed to start server '${name}':`, error);
293
- return false;
294
- }));
295
- await Promise.all(promises);
306
+ // Clean up any existing duplicates first
307
+ const processManager = mcp_process_manager_js_1.MCPProcessManager.getInstance();
308
+ processManager.killDuplicates();
309
+ console.log('✅ Starting all MCP servers (singleton protected with resource limits)...');
310
+ console.log(processManager.getResourceSummary());
311
+ // Start servers sequentially with delay to avoid resource spikes
312
+ let started = 0;
313
+ for (const name of Array.from(this.servers.keys())) {
314
+ try {
315
+ // Check resource limits before each start
316
+ if (!processManager.canSpawnServer()) {
317
+ console.warn(`⚠️ Skipping ${name} - resource limits reached`);
318
+ continue;
319
+ }
320
+ await this.startServer(name);
321
+ started++;
322
+ // Small delay between starts to avoid CPU spike
323
+ await new Promise(resolve => setTimeout(resolve, 200));
324
+ }
325
+ catch (error) {
326
+ console.error(`Failed to start server '${name}':`, error);
327
+ }
328
+ }
329
+ console.log(`✅ Started ${started}/${this.servers.size} MCP servers`);
330
+ console.log(processManager.getResourceSummary());
296
331
  }
297
332
  /**
298
333
  * Stop all running MCP servers
@@ -303,6 +338,12 @@ class MCPServerManager extends events_1.EventEmitter {
303
338
  return false;
304
339
  }));
305
340
  await Promise.all(promises);
341
+ // Release singleton lock after stopping all servers
342
+ const singletonLock = (0, mcp_singleton_lock_js_1.getMCPSingletonLock)();
343
+ if (singletonLock.isAcquired()) {
344
+ singletonLock.release();
345
+ console.log('✅ Released MCP singleton lock after stopping all servers');
346
+ }
306
347
  }
307
348
  /**
308
349
  * Get status of a specific server
@@ -15,6 +15,10 @@ export declare class MCPSingletonLock {
15
15
  * Release the singleton lock
16
16
  */
17
17
  release(): void;
18
+ /**
19
+ * Release the singleton lock without blocking (for graceful shutdown)
20
+ */
21
+ releaseAsync(): Promise<void>;
18
22
  /**
19
23
  * Check if lock is currently held by this process
20
24
  */
@@ -71,6 +71,18 @@ class MCPSingletonLock {
71
71
  }
72
72
  }
73
73
  }
74
+ /**
75
+ * Release the singleton lock without blocking (for graceful shutdown)
76
+ */
77
+ releaseAsync() {
78
+ return new Promise((resolve) => {
79
+ // Use setImmediate to avoid blocking
80
+ setImmediate(() => {
81
+ this.release();
82
+ resolve();
83
+ });
84
+ });
85
+ }
74
86
  /**
75
87
  * Check if lock is currently held by this process
76
88
  */
@@ -82,20 +94,35 @@ class MCPSingletonLock {
82
94
  */
83
95
  setupCleanupHandlers() {
84
96
  const cleanup = () => {
85
- this.release();
97
+ // Use non-blocking release to prevent hanging during shutdown
98
+ setImmediate(() => {
99
+ this.release();
100
+ });
86
101
  };
87
- process.on('exit', cleanup);
88
- process.on('SIGINT', cleanup);
89
- process.on('SIGTERM', cleanup);
90
- process.on('uncaughtException', (error) => {
102
+ // Remove existing handlers to prevent duplicate registrations
103
+ process.removeAllListeners('exit');
104
+ process.removeAllListeners('SIGINT');
105
+ process.removeAllListeners('SIGTERM');
106
+ process.once('exit', cleanup);
107
+ process.once('SIGINT', () => {
108
+ cleanup();
109
+ // Allow graceful exit after cleanup
110
+ setTimeout(() => process.exit(0), 100);
111
+ });
112
+ process.once('SIGTERM', () => {
113
+ cleanup();
114
+ // Allow graceful exit after cleanup
115
+ setTimeout(() => process.exit(0), 100);
116
+ });
117
+ process.once('uncaughtException', (error) => {
91
118
  logger.error('Uncaught exception, releasing MCP lock:', error);
92
119
  cleanup();
93
- process.exit(1);
120
+ setTimeout(() => process.exit(1), 100);
94
121
  });
95
- process.on('unhandledRejection', (reason) => {
122
+ process.once('unhandledRejection', (reason) => {
96
123
  logger.error('Unhandled rejection, releasing MCP lock:', reason);
97
124
  cleanup();
98
- process.exit(1);
125
+ setTimeout(() => process.exit(1), 100);
99
126
  });
100
127
  }
101
128
  /**
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Smart ML Data Fetcher
3
+ * Handles intelligent data fetching for ML training with batching and field discovery
4
+ * Prevents token limit errors and optimizes data retrieval
5
+ */
6
+ export interface MLDataFetchOptions {
7
+ table: string;
8
+ query?: string;
9
+ totalSamples?: number;
10
+ batchSize?: number;
11
+ fields?: string[];
12
+ discoverFields?: boolean;
13
+ includeContent?: boolean;
14
+ }
15
+ export interface FieldDiscoveryResult {
16
+ allFields: string[];
17
+ recommendedFields: string[];
18
+ sampleData: any[];
19
+ }
20
+ export interface BatchFetchResult {
21
+ data: any[];
22
+ totalFetched: number;
23
+ batchesProcessed: number;
24
+ fields: string[];
25
+ }
26
+ export declare class MLDataFetcher {
27
+ private operationsMCP;
28
+ constructor(operationsMCP: any);
29
+ /**
30
+ * Smart fetch with automatic field discovery and batching
31
+ */
32
+ smartFetch(options: MLDataFetchOptions): Promise<BatchFetchResult>;
33
+ /**
34
+ * Get total count of records matching query
35
+ */
36
+ private getRecordCount;
37
+ /**
38
+ * Discover available fields by sampling a few records
39
+ */
40
+ private discoverFields;
41
+ /**
42
+ * Fetch a single batch of data
43
+ */
44
+ private fetchBatch;
45
+ /**
46
+ * Extract data from MCP tool result
47
+ */
48
+ private extractDataFromResult;
49
+ /**
50
+ * Calculate optimal batch size based on data characteristics
51
+ */
52
+ private calculateOptimalBatchSize;
53
+ /**
54
+ * Select appropriate fields for ML training
55
+ */
56
+ private selectMLFields;
57
+ /**
58
+ * Get default fields for a table type
59
+ */
60
+ private getDefaultFields;
61
+ /**
62
+ * Get key fields that should always be included
63
+ */
64
+ private getKeyFields;
65
+ }
66
+ //# sourceMappingURL=ml-data-fetcher.d.ts.map
@@ -0,0 +1,288 @@
1
+ "use strict";
2
+ /**
3
+ * Smart ML Data Fetcher
4
+ * Handles intelligent data fetching for ML training with batching and field discovery
5
+ * Prevents token limit errors and optimizes data retrieval
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.MLDataFetcher = void 0;
9
+ const logger_js_1 = require("./logger.js");
10
+ const logger = new logger_js_1.Logger('MLDataFetcher');
11
+ class MLDataFetcher {
12
+ constructor(operationsMCP) {
13
+ this.operationsMCP = operationsMCP;
14
+ }
15
+ /**
16
+ * Smart fetch with automatic field discovery and batching
17
+ */
18
+ async smartFetch(options) {
19
+ const { table, query = '', totalSamples = 1000, batchSize = 100, fields, discoverFields = true, includeContent = true } = options;
20
+ logger.info(`🧠 Smart ML data fetch for ${table} - Target: ${totalSamples} samples`);
21
+ // Step 1: Get total count
22
+ const countResult = await this.getRecordCount(table, query);
23
+ logger.info(`📊 Total available records: ${countResult}`);
24
+ // Step 2: Discover fields if needed
25
+ let fieldsToFetch = fields;
26
+ if (!fieldsToFetch && discoverFields) {
27
+ const discovery = await this.discoverFields(table, query);
28
+ fieldsToFetch = discovery.recommendedFields;
29
+ logger.info(`🔍 Discovered ${discovery.allFields.length} fields, using ${fieldsToFetch.length} for ML`);
30
+ }
31
+ // Step 3: Calculate optimal batching strategy
32
+ const actualTotal = Math.min(totalSamples, countResult);
33
+ const optimalBatchSize = this.calculateOptimalBatchSize(actualTotal, batchSize, fieldsToFetch?.length || 10);
34
+ const numBatches = Math.ceil(actualTotal / optimalBatchSize);
35
+ logger.info(`📦 Fetching ${actualTotal} records in ${numBatches} batches of ${optimalBatchSize}`);
36
+ // Step 4: Fetch data in batches
37
+ const allData = [];
38
+ for (let batch = 0; batch < numBatches; batch++) {
39
+ const offset = batch * optimalBatchSize;
40
+ const limit = Math.min(optimalBatchSize, actualTotal - offset);
41
+ logger.info(` Batch ${batch + 1}/${numBatches}: Fetching ${limit} records (offset: ${offset})`);
42
+ try {
43
+ const batchData = await this.fetchBatch(table, query, limit, offset, fieldsToFetch, includeContent);
44
+ allData.push(...batchData);
45
+ // Small delay between batches to avoid overwhelming the API
46
+ if (batch < numBatches - 1) {
47
+ await new Promise(resolve => setTimeout(resolve, 200)); // Increased from 100ms to 200ms
48
+ }
49
+ }
50
+ catch (error) {
51
+ if (error.message?.includes('exceeds maximum allowed tokens')) {
52
+ // Reduce batch size and retry
53
+ logger.warn(`⚠️ Token limit hit, reducing batch size and retrying...`);
54
+ const smallerBatchSize = Math.floor(optimalBatchSize / 2);
55
+ return this.smartFetch({
56
+ ...options,
57
+ batchSize: smallerBatchSize
58
+ });
59
+ }
60
+ throw error;
61
+ }
62
+ }
63
+ return {
64
+ data: allData,
65
+ totalFetched: allData.length,
66
+ batchesProcessed: numBatches,
67
+ fields: fieldsToFetch || []
68
+ };
69
+ }
70
+ /**
71
+ * Get total count of records matching query
72
+ */
73
+ async getRecordCount(table, query) {
74
+ try {
75
+ const result = await this.operationsMCP.handleTool('snow_query_table', {
76
+ table,
77
+ query,
78
+ limit: 1,
79
+ include_content: false // Count only, no data
80
+ });
81
+ // Extract count from result
82
+ if (result?.content?.[0]?.text) {
83
+ const text = result.content[0].text;
84
+ const match = text.match(/Found (\d+) .* records/);
85
+ if (match) {
86
+ return parseInt(match[1], 10);
87
+ }
88
+ }
89
+ return 0;
90
+ }
91
+ catch (error) {
92
+ logger.error('Failed to get record count:', error);
93
+ return 0;
94
+ }
95
+ }
96
+ /**
97
+ * Discover available fields by sampling a few records
98
+ */
99
+ async discoverFields(table, query) {
100
+ logger.info('🔍 Discovering fields from sample records...');
101
+ try {
102
+ // Fetch just 3 records with all fields to discover schema
103
+ const result = await this.operationsMCP.handleTool('snow_query_table', {
104
+ table,
105
+ query,
106
+ limit: 3,
107
+ include_content: true
108
+ // No fields specified = get all fields
109
+ });
110
+ const sampleData = this.extractDataFromResult(result);
111
+ if (sampleData.length === 0) {
112
+ logger.warn('No sample data available for field discovery');
113
+ return {
114
+ allFields: [],
115
+ recommendedFields: this.getDefaultFields(table),
116
+ sampleData: []
117
+ };
118
+ }
119
+ // Extract all field names from sample
120
+ const allFields = Object.keys(sampleData[0] || {});
121
+ // Recommend fields for ML (exclude system fields and large text fields)
122
+ const recommendedFields = this.selectMLFields(allFields, sampleData, table);
123
+ return {
124
+ allFields,
125
+ recommendedFields,
126
+ sampleData
127
+ };
128
+ }
129
+ catch (error) {
130
+ logger.error('Field discovery failed:', error);
131
+ return {
132
+ allFields: [],
133
+ recommendedFields: this.getDefaultFields(table),
134
+ sampleData: []
135
+ };
136
+ }
137
+ }
138
+ /**
139
+ * Fetch a single batch of data
140
+ */
141
+ async fetchBatch(table, query, limit, offset, fields, includeContent = true) {
142
+ try {
143
+ // Build query with offset
144
+ const offsetQuery = query ? `${query}^ORDERBY${offset}` : `ORDERBY${offset}`;
145
+ const result = await this.operationsMCP.handleTool('snow_query_table', {
146
+ table,
147
+ query: offsetQuery,
148
+ limit,
149
+ fields,
150
+ include_content: includeContent
151
+ });
152
+ return this.extractDataFromResult(result);
153
+ }
154
+ catch (error) {
155
+ logger.error(`Failed to fetch batch (offset: ${offset}, limit: ${limit}):`, error);
156
+ return [];
157
+ }
158
+ }
159
+ /**
160
+ * Extract data from MCP tool result
161
+ */
162
+ extractDataFromResult(result) {
163
+ if (!result?.content?.[0]?.text) {
164
+ return [];
165
+ }
166
+ try {
167
+ const text = result.content[0].text;
168
+ // Try to parse as JSON first
169
+ if (text.includes('[') && text.includes(']')) {
170
+ const jsonMatch = text.match(/\[[\s\S]*\]/);
171
+ if (jsonMatch) {
172
+ return JSON.parse(jsonMatch[0]);
173
+ }
174
+ }
175
+ // Try to extract from formatted output
176
+ const lines = text.split('\n');
177
+ const data = [];
178
+ let currentRecord = null;
179
+ for (const line of lines) {
180
+ if (line.includes('number:') || line.includes('sys_id:')) {
181
+ if (currentRecord) {
182
+ data.push(currentRecord);
183
+ }
184
+ currentRecord = {};
185
+ }
186
+ if (currentRecord && line.includes(':')) {
187
+ const [key, ...valueParts] = line.split(':');
188
+ const cleanKey = key.trim().replace(/^[-\s]+/, '');
189
+ const value = valueParts.join(':').trim();
190
+ if (cleanKey && value) {
191
+ currentRecord[cleanKey] = value;
192
+ }
193
+ }
194
+ }
195
+ if (currentRecord && Object.keys(currentRecord).length > 0) {
196
+ data.push(currentRecord);
197
+ }
198
+ return data;
199
+ }
200
+ catch (error) {
201
+ logger.error('Failed to extract data from result:', error);
202
+ return [];
203
+ }
204
+ }
205
+ /**
206
+ * Calculate optimal batch size based on data characteristics
207
+ */
208
+ calculateOptimalBatchSize(totalRecords, requestedBatchSize, numFields) {
209
+ // Estimate tokens per record (rough approximation)
210
+ const avgTokensPerField = 10; // Conservative estimate
211
+ const tokensPerRecord = numFields * avgTokensPerField;
212
+ const maxTokensPerBatch = 20000; // Leave buffer below 25000 limit
213
+ // Calculate max records per batch based on token limit
214
+ const maxRecordsPerBatch = Math.floor(maxTokensPerBatch / tokensPerRecord);
215
+ // Use the smaller of requested batch size and calculated max
216
+ const optimalSize = Math.min(requestedBatchSize, maxRecordsPerBatch);
217
+ // Ensure at least 10 records per batch but not more than total
218
+ return Math.max(10, Math.min(optimalSize, totalRecords));
219
+ }
220
+ /**
221
+ * Select appropriate fields for ML training
222
+ */
223
+ selectMLFields(allFields, sampleData, table) {
224
+ const excluded = new Set([
225
+ 'sys_id', 'sys_created_on', 'sys_created_by', 'sys_updated_on', 'sys_updated_by',
226
+ 'sys_mod_count', 'sys_tags', 'sys_package', 'sys_policy', 'sys_scope',
227
+ 'sys_domain', 'sys_domain_path', 'sys_class_name'
228
+ ]);
229
+ const mlFields = allFields.filter(field => {
230
+ // Exclude system fields
231
+ if (excluded.has(field))
232
+ return false;
233
+ // Check if field has useful data in samples
234
+ const hasData = sampleData.some(record => {
235
+ const value = record[field];
236
+ return value && value !== 'null' && value !== '';
237
+ });
238
+ return hasData;
239
+ });
240
+ // Always include key fields for the table type
241
+ const keyFields = this.getKeyFields(table);
242
+ const combinedFields = [...new Set([...keyFields, ...mlFields])];
243
+ // Limit to 20 most relevant fields to avoid token issues
244
+ return combinedFields.slice(0, 20);
245
+ }
246
+ /**
247
+ * Get default fields for a table type
248
+ */
249
+ getDefaultFields(table) {
250
+ const fieldMap = {
251
+ incident: [
252
+ 'number', 'short_description', 'description', 'category', 'subcategory',
253
+ 'priority', 'urgency', 'impact', 'state', 'assignment_group',
254
+ 'assigned_to', 'caller_id', 'opened_at', 'resolved_at'
255
+ ],
256
+ change_request: [
257
+ 'number', 'short_description', 'description', 'type', 'category',
258
+ 'priority', 'risk', 'impact', 'state', 'assignment_group',
259
+ 'assigned_to', 'requested_by', 'start_date', 'end_date'
260
+ ],
261
+ problem: [
262
+ 'number', 'short_description', 'description', 'category', 'subcategory',
263
+ 'priority', 'urgency', 'impact', 'state', 'assignment_group',
264
+ 'assigned_to', 'opened_at', 'known_error'
265
+ ],
266
+ sc_request: [
267
+ 'number', 'short_description', 'description', 'request_state', 'approval',
268
+ 'requested_for', 'requested_by', 'assignment_group', 'assigned_to',
269
+ 'opened_at', 'closed_at'
270
+ ]
271
+ };
272
+ return fieldMap[table] || ['number', 'short_description', 'state', 'priority'];
273
+ }
274
+ /**
275
+ * Get key fields that should always be included
276
+ */
277
+ getKeyFields(table) {
278
+ const keyFieldMap = {
279
+ incident: ['number', 'short_description', 'category', 'priority', 'state'],
280
+ change_request: ['number', 'short_description', 'type', 'risk', 'state'],
281
+ problem: ['number', 'short_description', 'category', 'priority', 'state'],
282
+ sc_request: ['number', 'short_description', 'request_state', 'approval']
283
+ };
284
+ return keyFieldMap[table] || ['number', 'short_description', 'state'];
285
+ }
286
+ }
287
+ exports.MLDataFetcher = MLDataFetcher;
288
+ //# sourceMappingURL=ml-data-fetcher.js.map
@@ -13,10 +13,10 @@ const axios_1 = __importDefault(require("axios"));
13
13
  const https_1 = __importDefault(require("https"));
14
14
  const snow_oauth_1 = require("./snow-oauth");
15
15
  const action_type_cache_1 = require("./action-type-cache");
16
- const snow_flow_config_js_1 = require("../config/snow-flow-config.js");
17
16
  const widget_template_generator_js_1 = require("./widget-template-generator.js");
18
17
  const logger_1 = require("./logger");
19
18
  const unified_auth_store_js_1 = require("./unified-auth-store.js");
19
+ const timeout_manager_js_1 = require("./timeout-manager.js");
20
20
  class ServiceNowClient {
21
21
  constructor() {
22
22
  this.credentials = null;
@@ -68,8 +68,10 @@ class ServiceNowClient {
68
68
  maxVersion: 'TLSv1.3',
69
69
  minVersion: 'TLSv1.2'
70
70
  });
71
+ // Use intelligent timeout based on operation type (default to TABLE_QUERY)
72
+ const defaultTimeout = (0, timeout_manager_js_1.getTimeoutConfig)(timeout_manager_js_1.OperationType.TABLE_QUERY).baseTimeout;
71
73
  this.client = axios_1.default.create({
72
- timeout: snow_flow_config_js_1.snowFlowConfig.servicenow.timeout,
74
+ timeout: parseInt(process.env.SNOW_API_TIMEOUT || String(defaultTimeout)),
73
75
  headers: {
74
76
  'Content-Type': 'application/json',
75
77
  'Accept': 'application/json'
@@ -979,21 +981,33 @@ class ServiceNowClient {
979
981
  async searchRecords(table, query, limit = 10) {
980
982
  try {
981
983
  await this.ensureAuthenticated();
982
- const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/${table}`, {
983
- params: {
984
- sysparm_query: query,
985
- sysparm_limit: limit
986
- }
984
+ // Detect operation type for intelligent timeout
985
+ const operationType = (0, timeout_manager_js_1.detectOperationType)({
986
+ action: 'query',
987
+ table,
988
+ limit
987
989
  });
990
+ // Use retry wrapper with intelligent timeout
991
+ const result = await (0, timeout_manager_js_1.withRetry)(async () => {
992
+ const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/${table}`, {
993
+ params: {
994
+ sysparm_query: query,
995
+ sysparm_limit: limit
996
+ },
997
+ // Override timeout for this specific request
998
+ timeout: (0, timeout_manager_js_1.getTimeoutConfig)(operationType).baseTimeout
999
+ });
1000
+ return response;
1001
+ }, operationType, `Search ${table} (${limit} records)`);
988
1002
  return {
989
1003
  success: true,
990
1004
  data: {
991
- result: response.data.result || []
1005
+ result: result.data.result || []
992
1006
  }
993
1007
  };
994
1008
  }
995
1009
  catch (error) {
996
- console.error(`Failed to search records in ${table}:`, error);
1010
+ this.logger.error(`Failed to search records in ${table}:`, error);
997
1011
  return {
998
1012
  success: false,
999
1013
  error: error instanceof Error ? error.message : String(error)
@@ -1006,22 +1020,34 @@ class ServiceNowClient {
1006
1020
  async searchRecordsWithOffset(table, query, limit = 10, offset = 0) {
1007
1021
  try {
1008
1022
  await this.ensureAuthenticated();
1009
- const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/${table}`, {
1010
- params: {
1011
- sysparm_query: query,
1012
- sysparm_limit: limit,
1013
- sysparm_offset: offset
1014
- }
1023
+ // Detect operation type for intelligent timeout
1024
+ const operationType = (0, timeout_manager_js_1.detectOperationType)({
1025
+ action: 'query',
1026
+ table,
1027
+ limit
1015
1028
  });
1029
+ // Use retry wrapper with intelligent timeout
1030
+ const result = await (0, timeout_manager_js_1.withRetry)(async () => {
1031
+ const response = await this.client.get(`${this.getBaseUrl()}/api/now/table/${table}`, {
1032
+ params: {
1033
+ sysparm_query: query,
1034
+ sysparm_limit: limit,
1035
+ sysparm_offset: offset
1036
+ },
1037
+ // Override timeout for this specific request
1038
+ timeout: (0, timeout_manager_js_1.getTimeoutConfig)(operationType).baseTimeout
1039
+ });
1040
+ return response;
1041
+ }, operationType, `Search ${table} with offset ${offset}`);
1016
1042
  return {
1017
1043
  success: true,
1018
1044
  data: {
1019
- result: response.data.result || []
1045
+ result: result.data.result || []
1020
1046
  }
1021
1047
  };
1022
1048
  }
1023
1049
  catch (error) {
1024
- console.error(`Failed to search records in ${table} with offset ${offset}:`, error);
1050
+ this.logger.error(`Failed to search records in ${table} with offset ${offset}:`, error);
1025
1051
  return {
1026
1052
  success: false,
1027
1053
  error: error instanceof Error ? error.message : String(error)