@probelabs/probe-chat 0.6.0-rc100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +338 -0
- package/TRACING.md +226 -0
- package/appTracer.js +947 -0
- package/auth.js +76 -0
- package/bin/probe-chat.js +13 -0
- package/cancelRequest.js +84 -0
- package/fileSpanExporter.js +183 -0
- package/implement/README.md +228 -0
- package/implement/backends/AiderBackend.js +750 -0
- package/implement/backends/BaseBackend.js +276 -0
- package/implement/backends/ClaudeCodeBackend.js +767 -0
- package/implement/backends/MockBackend.js +237 -0
- package/implement/backends/registry.js +85 -0
- package/implement/core/BackendManager.js +567 -0
- package/implement/core/ImplementTool.js +354 -0
- package/implement/core/config.js +428 -0
- package/implement/core/timeouts.js +58 -0
- package/implement/core/utils.js +496 -0
- package/implement/types/BackendTypes.js +126 -0
- package/index.html +3751 -0
- package/index.js +582 -0
- package/logo.png +0 -0
- package/package.json +101 -0
- package/probeChat.js +269 -0
- package/probeTool.js +714 -0
- package/storage/JsonChatStorage.js +476 -0
- package/telemetry.js +287 -0
- package/test/integration/chatFlows.test.js +320 -0
- package/test/integration/toolCalling.test.js +471 -0
- package/test/mocks/mockLLMProvider.js +269 -0
- package/test/test-backends.js +90 -0
- package/test/testUtils.js +530 -0
- package/test/unit/backendTimeout.test.js +161 -0
- package/test/verify-tests.js +118 -0
- package/tokenCounter.js +419 -0
- package/tokenUsageDisplay.js +134 -0
- package/tools.js +186 -0
- package/webServer.js +1103 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration management for the implementation tool
|
|
3
|
+
* @module config
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { promisify } from 'util';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { TIMEOUTS, getDefaultTimeoutMs, secondsToMs, isValidTimeout } from './timeouts.js';
|
|
11
|
+
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
|
|
14
|
+
const readFile = promisify(fs.readFile);
|
|
15
|
+
const writeFile = promisify(fs.writeFile);
|
|
16
|
+
const exists = promisify(fs.exists);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Configuration manager for implementation backends
|
|
20
|
+
* @class
|
|
21
|
+
*/
|
|
22
|
+
class ConfigManager {
|
|
23
|
+
constructor() {
|
|
24
|
+
this.config = null;
|
|
25
|
+
this.configPath = null;
|
|
26
|
+
this.watchers = new Map();
|
|
27
|
+
this.changeCallbacks = [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Initialize configuration
|
|
32
|
+
* @param {string} [configPath] - Path to configuration file
|
|
33
|
+
* @returns {Promise<void>}
|
|
34
|
+
*/
|
|
35
|
+
async initialize(configPath = null) {
|
|
36
|
+
// Determine config path
|
|
37
|
+
this.configPath = this.resolveConfigPath(configPath);
|
|
38
|
+
|
|
39
|
+
// Load configuration
|
|
40
|
+
await this.loadConfig();
|
|
41
|
+
|
|
42
|
+
// Apply environment overrides
|
|
43
|
+
this.applyEnvironmentOverrides();
|
|
44
|
+
|
|
45
|
+
// Set up file watching
|
|
46
|
+
if (this.configPath && fs.existsSync(this.configPath)) {
|
|
47
|
+
this.setupWatcher();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resolve configuration file path
|
|
53
|
+
* @param {string} [providedPath] - User-provided path
|
|
54
|
+
* @returns {string|null}
|
|
55
|
+
* @private
|
|
56
|
+
*/
|
|
57
|
+
resolveConfigPath(providedPath) {
|
|
58
|
+
// Priority order:
|
|
59
|
+
// 1. Provided path
|
|
60
|
+
// 2. Environment variable
|
|
61
|
+
// 3. Local config file
|
|
62
|
+
// 4. Default config file
|
|
63
|
+
|
|
64
|
+
if (providedPath && fs.existsSync(providedPath)) {
|
|
65
|
+
return providedPath;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (process.env.IMPLEMENT_TOOL_CONFIG_PATH) {
|
|
69
|
+
const envPath = process.env.IMPLEMENT_TOOL_CONFIG_PATH;
|
|
70
|
+
if (fs.existsSync(envPath)) {
|
|
71
|
+
return envPath;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check for local config in current directory
|
|
76
|
+
const localConfig = path.join(process.cwd(), 'implement-config.json');
|
|
77
|
+
if (fs.existsSync(localConfig)) {
|
|
78
|
+
return localConfig;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Fall back to default config
|
|
82
|
+
const defaultConfig = path.join(__dirname, '..', 'config', 'default.json');
|
|
83
|
+
if (fs.existsSync(defaultConfig)) {
|
|
84
|
+
return defaultConfig;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Load configuration from file
|
|
92
|
+
* @returns {Promise<void>}
|
|
93
|
+
* @private
|
|
94
|
+
*/
|
|
95
|
+
async loadConfig() {
|
|
96
|
+
if (this.configPath && fs.existsSync(this.configPath)) {
|
|
97
|
+
try {
|
|
98
|
+
const configData = await readFile(this.configPath, 'utf8');
|
|
99
|
+
this.config = JSON.parse(configData);
|
|
100
|
+
console.error(`Loaded configuration from: ${this.configPath}`);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error(`Failed to load configuration from ${this.configPath}:`, error.message);
|
|
103
|
+
this.config = this.getDefaultConfig();
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
console.error('Using default configuration');
|
|
107
|
+
this.config = this.getDefaultConfig();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get default configuration
|
|
113
|
+
* @returns {Object}
|
|
114
|
+
* @private
|
|
115
|
+
*/
|
|
116
|
+
getDefaultConfig() {
|
|
117
|
+
return {
|
|
118
|
+
implement: {
|
|
119
|
+
defaultBackend: 'aider',
|
|
120
|
+
selectionStrategy: 'auto',
|
|
121
|
+
maxConcurrentSessions: 3,
|
|
122
|
+
timeout: getDefaultTimeoutMs(), // Use centralized default (20 minutes)
|
|
123
|
+
retryAttempts: 2,
|
|
124
|
+
retryDelay: 5000
|
|
125
|
+
},
|
|
126
|
+
backends: {
|
|
127
|
+
aider: {
|
|
128
|
+
command: 'aider',
|
|
129
|
+
timeout: getDefaultTimeoutMs(), // Use centralized default (20 minutes)
|
|
130
|
+
maxOutputSize: 10485760,
|
|
131
|
+
additionalArgs: [],
|
|
132
|
+
environment: {},
|
|
133
|
+
autoCommit: false,
|
|
134
|
+
modelSelection: 'auto'
|
|
135
|
+
},
|
|
136
|
+
'claude-code': {
|
|
137
|
+
timeout: getDefaultTimeoutMs(), // Use centralized default (20 minutes)
|
|
138
|
+
maxTokens: 8000,
|
|
139
|
+
temperature: 0.3,
|
|
140
|
+
model: 'claude-3-5-sonnet-20241022',
|
|
141
|
+
systemPrompt: null,
|
|
142
|
+
tools: ['edit', 'search', 'bash'],
|
|
143
|
+
maxTurns: 100
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Apply environment variable overrides
|
|
151
|
+
* @private
|
|
152
|
+
*/
|
|
153
|
+
applyEnvironmentOverrides() {
|
|
154
|
+
// Backend selection
|
|
155
|
+
if (process.env.IMPLEMENT_TOOL_BACKEND) {
|
|
156
|
+
this.config.implement.defaultBackend = process.env.IMPLEMENT_TOOL_BACKEND;
|
|
157
|
+
console.error(`[ImplementConfig] Setting default backend from env: ${process.env.IMPLEMENT_TOOL_BACKEND}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (process.env.IMPLEMENT_TOOL_FALLBACKS) {
|
|
161
|
+
this.config.implement.fallbackBackends = process.env.IMPLEMENT_TOOL_FALLBACKS
|
|
162
|
+
.split(',')
|
|
163
|
+
.map(s => s.trim())
|
|
164
|
+
.filter(Boolean);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (process.env.IMPLEMENT_TOOL_SELECTION_STRATEGY) {
|
|
168
|
+
this.config.implement.selectionStrategy = process.env.IMPLEMENT_TOOL_SELECTION_STRATEGY;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (process.env.IMPLEMENT_TOOL_TIMEOUT) {
|
|
172
|
+
// Convert seconds to milliseconds for backend compatibility
|
|
173
|
+
const timeoutSeconds = parseInt(process.env.IMPLEMENT_TOOL_TIMEOUT, 10);
|
|
174
|
+
|
|
175
|
+
if (isNaN(timeoutSeconds)) {
|
|
176
|
+
console.warn(`[Config] Invalid IMPLEMENT_TOOL_TIMEOUT value: ${process.env.IMPLEMENT_TOOL_TIMEOUT}. Using default: ${TIMEOUTS.IMPLEMENT_DEFAULT}s`);
|
|
177
|
+
} else if (!isValidTimeout(timeoutSeconds)) {
|
|
178
|
+
console.warn(`[Config] IMPLEMENT_TOOL_TIMEOUT ${timeoutSeconds}s outside valid range ${TIMEOUTS.IMPLEMENT_MINIMUM}-${TIMEOUTS.IMPLEMENT_MAXIMUM}s. Using default: ${TIMEOUTS.IMPLEMENT_DEFAULT}s`);
|
|
179
|
+
} else {
|
|
180
|
+
this.config.implement.timeout = secondsToMs(timeoutSeconds);
|
|
181
|
+
// Log message removed to prevent stdout pollution
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Aider backend configuration
|
|
186
|
+
if (process.env.AIDER_MODEL) {
|
|
187
|
+
this.config.backends.aider = this.config.backends.aider || {};
|
|
188
|
+
this.config.backends.aider.model = process.env.AIDER_MODEL;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (process.env.AIDER_TIMEOUT) {
|
|
192
|
+
this.config.backends.aider = this.config.backends.aider || {};
|
|
193
|
+
this.config.backends.aider.timeout = parseInt(process.env.AIDER_TIMEOUT, 10);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (process.env.AIDER_AUTO_COMMIT) {
|
|
197
|
+
this.config.backends.aider = this.config.backends.aider || {};
|
|
198
|
+
this.config.backends.aider.autoCommit = process.env.AIDER_AUTO_COMMIT === 'true';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (process.env.AIDER_ADDITIONAL_ARGS) {
|
|
202
|
+
this.config.backends.aider = this.config.backends.aider || {};
|
|
203
|
+
this.config.backends.aider.additionalArgs = process.env.AIDER_ADDITIONAL_ARGS
|
|
204
|
+
.split(',')
|
|
205
|
+
.map(s => s.trim())
|
|
206
|
+
.filter(Boolean);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Claude Code backend configuration
|
|
210
|
+
if (process.env.CLAUDE_CODE_MODEL) {
|
|
211
|
+
this.config.backends['claude-code'] = this.config.backends['claude-code'] || {};
|
|
212
|
+
this.config.backends['claude-code'].model = process.env.CLAUDE_CODE_MODEL;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (process.env.CLAUDE_CODE_MAX_TOKENS) {
|
|
216
|
+
this.config.backends['claude-code'] = this.config.backends['claude-code'] || {};
|
|
217
|
+
this.config.backends['claude-code'].maxTokens = parseInt(process.env.CLAUDE_CODE_MAX_TOKENS, 10);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (process.env.CLAUDE_CODE_TEMPERATURE) {
|
|
221
|
+
this.config.backends['claude-code'] = this.config.backends['claude-code'] || {};
|
|
222
|
+
this.config.backends['claude-code'].temperature = parseFloat(process.env.CLAUDE_CODE_TEMPERATURE);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (process.env.CLAUDE_CODE_MAX_TURNS) {
|
|
226
|
+
this.config.backends['claude-code'] = this.config.backends['claude-code'] || {};
|
|
227
|
+
this.config.backends['claude-code'].maxTurns = parseInt(process.env.CLAUDE_CODE_MAX_TURNS, 10);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Set up file watcher for configuration changes
|
|
233
|
+
* @private
|
|
234
|
+
*/
|
|
235
|
+
setupWatcher() {
|
|
236
|
+
if (!this.configPath) return;
|
|
237
|
+
|
|
238
|
+
fs.watchFile(this.configPath, { interval: 2000 }, async (curr, prev) => {
|
|
239
|
+
if (curr.mtime !== prev.mtime) {
|
|
240
|
+
console.error('Configuration file changed, reloading...');
|
|
241
|
+
await this.reloadConfig();
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Reload configuration from file
|
|
248
|
+
* @returns {Promise<void>}
|
|
249
|
+
*/
|
|
250
|
+
async reloadConfig() {
|
|
251
|
+
try {
|
|
252
|
+
const oldConfig = JSON.stringify(this.config);
|
|
253
|
+
await this.loadConfig();
|
|
254
|
+
this.applyEnvironmentOverrides();
|
|
255
|
+
|
|
256
|
+
const newConfig = JSON.stringify(this.config);
|
|
257
|
+
if (oldConfig !== newConfig) {
|
|
258
|
+
this.notifyChangeCallbacks();
|
|
259
|
+
}
|
|
260
|
+
} catch (error) {
|
|
261
|
+
console.error('Failed to reload configuration:', error);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Register a callback for configuration changes
|
|
267
|
+
* @param {Function} callback - Callback function
|
|
268
|
+
*/
|
|
269
|
+
onChange(callback) {
|
|
270
|
+
this.changeCallbacks.push(callback);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Notify all change callbacks
|
|
275
|
+
* @private
|
|
276
|
+
*/
|
|
277
|
+
notifyChangeCallbacks() {
|
|
278
|
+
for (const callback of this.changeCallbacks) {
|
|
279
|
+
try {
|
|
280
|
+
callback(this.config);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
console.error('Error in configuration change callback:', error);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Get configuration value by path
|
|
289
|
+
* @param {string} [path] - Dot-separated path (e.g., 'implement.defaultBackend')
|
|
290
|
+
* @returns {*}
|
|
291
|
+
*/
|
|
292
|
+
get(path = null) {
|
|
293
|
+
if (!path) {
|
|
294
|
+
return this.config;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const parts = path.split('.');
|
|
298
|
+
let value = this.config;
|
|
299
|
+
|
|
300
|
+
for (const part of parts) {
|
|
301
|
+
if (value && typeof value === 'object' && part in value) {
|
|
302
|
+
value = value[part];
|
|
303
|
+
} else {
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return value;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Set configuration value by path
|
|
313
|
+
* @param {string} path - Dot-separated path
|
|
314
|
+
* @param {*} value - Value to set
|
|
315
|
+
*/
|
|
316
|
+
set(path, value) {
|
|
317
|
+
const parts = path.split('.');
|
|
318
|
+
const lastPart = parts.pop();
|
|
319
|
+
|
|
320
|
+
let target = this.config;
|
|
321
|
+
for (const part of parts) {
|
|
322
|
+
if (!(part in target) || typeof target[part] !== 'object') {
|
|
323
|
+
target[part] = {};
|
|
324
|
+
}
|
|
325
|
+
target = target[part];
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
target[lastPart] = value;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Save configuration to file
|
|
333
|
+
* @param {string} [path] - Path to save to (defaults to current config path)
|
|
334
|
+
* @returns {Promise<void>}
|
|
335
|
+
*/
|
|
336
|
+
async save(path = null) {
|
|
337
|
+
const savePath = path || this.configPath;
|
|
338
|
+
if (!savePath) {
|
|
339
|
+
throw new Error('No configuration file path specified');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
const configData = JSON.stringify(this.config, null, 2);
|
|
344
|
+
await writeFile(savePath, configData, 'utf8');
|
|
345
|
+
console.error(`Configuration saved to: ${savePath}`);
|
|
346
|
+
} catch (error) {
|
|
347
|
+
throw new Error(`Failed to save configuration: ${error.message}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Get backend-specific configuration
|
|
353
|
+
* @param {string} backendName - Backend name
|
|
354
|
+
* @returns {Object}
|
|
355
|
+
*/
|
|
356
|
+
getBackendConfig(backendName) {
|
|
357
|
+
return this.config.backends?.[backendName] || {};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Get implementation tool configuration
|
|
362
|
+
* @returns {Object}
|
|
363
|
+
*/
|
|
364
|
+
getImplementConfig() {
|
|
365
|
+
return this.config.implement || {};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Validate configuration
|
|
370
|
+
* @returns {Object} Validation result
|
|
371
|
+
*/
|
|
372
|
+
validate() {
|
|
373
|
+
const errors = [];
|
|
374
|
+
const warnings = [];
|
|
375
|
+
|
|
376
|
+
// Check required fields
|
|
377
|
+
if (!this.config.implement?.defaultBackend) {
|
|
378
|
+
errors.push('implement.defaultBackend is required');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Check backend configurations exist
|
|
382
|
+
const defaultBackend = this.config.implement?.defaultBackend;
|
|
383
|
+
if (defaultBackend && !this.config.backends?.[defaultBackend]) {
|
|
384
|
+
warnings.push(`Configuration for default backend '${defaultBackend}' not found`);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Check fallback backends
|
|
388
|
+
const fallbackBackends = this.config.implement?.fallbackBackends || [];
|
|
389
|
+
for (const backend of fallbackBackends) {
|
|
390
|
+
if (!this.config.backends?.[backend]) {
|
|
391
|
+
warnings.push(`Configuration for fallback backend '${backend}' not found`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Validate selection strategy
|
|
396
|
+
const validStrategies = ['auto', 'preference', 'capability'];
|
|
397
|
+
const strategy = this.config.implement?.selectionStrategy;
|
|
398
|
+
if (strategy && !validStrategies.includes(strategy)) {
|
|
399
|
+
errors.push(`Invalid selection strategy: ${strategy}. Must be one of: ${validStrategies.join(', ')}`);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
valid: errors.length === 0,
|
|
404
|
+
errors,
|
|
405
|
+
warnings
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Clean up resources
|
|
411
|
+
*/
|
|
412
|
+
cleanup() {
|
|
413
|
+
if (this.configPath) {
|
|
414
|
+
fs.unwatchFile(this.configPath);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
this.changeCallbacks = [];
|
|
418
|
+
this.watchers.clear();
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Create singleton instance
|
|
423
|
+
const configManager = new ConfigManager();
|
|
424
|
+
|
|
425
|
+
export {
|
|
426
|
+
ConfigManager,
|
|
427
|
+
configManager
|
|
428
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized timeout configuration for the implementation system
|
|
3
|
+
*
|
|
4
|
+
* This file defines all timeout values used across the implementation pipeline
|
|
5
|
+
* to ensure consistency and maintainability.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const TIMEOUTS = {
|
|
9
|
+
// Main implementation timeouts (in seconds - user-friendly)
|
|
10
|
+
IMPLEMENT_DEFAULT: 1200, // 20 minutes - default for Claude Code/Aider execution
|
|
11
|
+
IMPLEMENT_MINIMUM: 60, // 1 minute - minimum allowed timeout
|
|
12
|
+
IMPLEMENT_MAXIMUM: 3600, // 1 hour - maximum allowed timeout
|
|
13
|
+
|
|
14
|
+
// Quick verification checks (in milliseconds)
|
|
15
|
+
VERSION_CHECK: 5000, // 5 seconds - claude --version, aider --version
|
|
16
|
+
PATH_CHECK: 2000, // 2 seconds - command existence checks
|
|
17
|
+
NPM_CHECK: 5000, // 5 seconds - npm operations
|
|
18
|
+
WSL_CHECK: 2000, // 2 seconds - WSL availability checks
|
|
19
|
+
|
|
20
|
+
// Network operations (in milliseconds)
|
|
21
|
+
HTTP_REQUEST: 10000, // 10 seconds - GitHub URLs, remote requests
|
|
22
|
+
FILE_FLUSH: 5000, // 5 seconds - file operations and flushing
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Convert seconds to milliseconds for internal use
|
|
27
|
+
* @param {number} seconds - Timeout in seconds
|
|
28
|
+
* @returns {number} Timeout in milliseconds
|
|
29
|
+
*/
|
|
30
|
+
export function secondsToMs(seconds) {
|
|
31
|
+
return seconds * 1000;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Convert milliseconds to seconds for user display
|
|
36
|
+
* @param {number} milliseconds - Timeout in milliseconds
|
|
37
|
+
* @returns {number} Timeout in seconds
|
|
38
|
+
*/
|
|
39
|
+
export function msToSeconds(milliseconds) {
|
|
40
|
+
return Math.floor(milliseconds / 1000);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Validate timeout value is within acceptable bounds
|
|
45
|
+
* @param {number} seconds - Timeout in seconds
|
|
46
|
+
* @returns {boolean} True if valid
|
|
47
|
+
*/
|
|
48
|
+
export function isValidTimeout(seconds) {
|
|
49
|
+
return seconds >= TIMEOUTS.IMPLEMENT_MINIMUM && seconds <= TIMEOUTS.IMPLEMENT_MAXIMUM;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get default timeout in milliseconds for internal use
|
|
54
|
+
* @returns {number} Default timeout in milliseconds
|
|
55
|
+
*/
|
|
56
|
+
export function getDefaultTimeoutMs() {
|
|
57
|
+
return secondsToMs(TIMEOUTS.IMPLEMENT_DEFAULT);
|
|
58
|
+
}
|