nexus-agents 2.27.0 → 2.29.0
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/LICENSE +27 -0
- package/README.md +11 -10
- package/dist/chunk-5VZLXMO7.js +838 -0
- package/dist/chunk-5VZLXMO7.js.map +1 -0
- package/dist/chunk-633WH2ML.js +127 -0
- package/dist/chunk-633WH2ML.js.map +1 -0
- package/dist/chunk-7F6HYUIY.js +327 -0
- package/dist/chunk-7F6HYUIY.js.map +1 -0
- package/dist/chunk-CLYZ7FWP.js +30 -0
- package/dist/chunk-CLYZ7FWP.js.map +1 -0
- package/dist/{chunk-WZACXZGT.js → chunk-DAMRMAM2.js} +19543 -35952
- package/dist/chunk-DAMRMAM2.js.map +1 -0
- package/dist/{chunk-VL7L56MU.js → chunk-HH5LVGEE.js} +6 -6
- package/dist/{chunk-6QLUXEA6.js → chunk-HWDBNDUX.js} +2 -2
- package/dist/chunk-I6YDS23R.js +354 -0
- package/dist/chunk-I6YDS23R.js.map +1 -0
- package/dist/{chunk-WF5CWL2N.js → chunk-IMWYKX4H.js} +4377 -4190
- package/dist/chunk-IMWYKX4H.js.map +1 -0
- package/dist/chunk-KGDG6PWZ.js +61 -0
- package/dist/chunk-KGDG6PWZ.js.map +1 -0
- package/dist/chunk-POBO4G2P.js +3700 -0
- package/dist/chunk-POBO4G2P.js.map +1 -0
- package/dist/chunk-S3BKWNST.js +13475 -0
- package/dist/chunk-S3BKWNST.js.map +1 -0
- package/dist/chunk-T7PU3NPQ.js +42 -0
- package/dist/chunk-T7PU3NPQ.js.map +1 -0
- package/dist/{chunk-4LM3YGJF.js → chunk-WSK4VSXP.js} +2 -2
- package/dist/{chunk-RNQRE45M.js → chunk-ZBZJHXRT.js} +181 -1119
- package/dist/chunk-ZBZJHXRT.js.map +1 -0
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +1410 -659
- package/dist/cli.js.map +1 -1
- package/dist/composite-router-YPRWVTRB.js +17 -0
- package/dist/consensus-vote-DBE6RNZG.js +23 -0
- package/dist/{dist-Y5F6UM2N.js → dist-7PQR2BQB.js} +1584 -1443
- package/dist/dist-7PQR2BQB.js.map +1 -0
- package/dist/{doctor-deep-EN3V4PXO.js → doctor-deep-AWE7SRU6.js} +4 -3
- package/dist/expert-config-FHNBQRX2.js +22 -0
- package/dist/expert-config-FHNBQRX2.js.map +1 -0
- package/dist/factory-O5C7ZBZO.js +13 -0
- package/dist/factory-O5C7ZBZO.js.map +1 -0
- package/dist/factory-PCHGQ3ZG.js +16 -0
- package/dist/factory-PCHGQ3ZG.js.map +1 -0
- package/dist/index.d.ts +3851 -9022
- package/dist/index.js +505 -318
- package/dist/index.js.map +1 -1
- package/dist/learning-persistence-WMWZJZ35.js +16 -0
- package/dist/learning-persistence-WMWZJZ35.js.map +1 -0
- package/dist/mcp-config-AUZQPUBY.js +12 -0
- package/dist/mcp-config-AUZQPUBY.js.map +1 -0
- package/dist/{model-capabilities-types-CSWO27YN.d.ts → model-capabilities-types-B57GZryc.d.ts} +1 -1
- package/dist/routing-memory-QY3XMU2R.js +13 -0
- package/dist/routing-memory-QY3XMU2R.js.map +1 -0
- package/dist/session-memory-3MBCE5KS.js +22 -0
- package/dist/session-memory-3MBCE5KS.js.map +1 -0
- package/dist/{setup-command-ZN7DSP53.js → setup-command-IQ4MD3FT.js} +8 -5
- package/dist/setup-command-IQ4MD3FT.js.map +1 -0
- package/dist/setup-config-5YUPLDXF.js +10 -0
- package/dist/setup-config-5YUPLDXF.js.map +1 -0
- package/dist/weather-report-CC2C4KAX.js +15 -0
- package/dist/weather-report-CC2C4KAX.js.map +1 -0
- package/package.json +33 -36
- package/dist/chunk-RNQRE45M.js.map +0 -1
- package/dist/chunk-WF5CWL2N.js.map +0 -1
- package/dist/chunk-WZACXZGT.js.map +0 -1
- package/dist/dist-Y5F6UM2N.js.map +0 -1
- package/dist/setup-config-ZOJUDXNP.js +0 -9
- /package/dist/{chunk-VL7L56MU.js.map → chunk-HH5LVGEE.js.map} +0 -0
- /package/dist/{chunk-6QLUXEA6.js.map → chunk-HWDBNDUX.js.map} +0 -0
- /package/dist/{chunk-4LM3YGJF.js.map → chunk-WSK4VSXP.js.map} +0 -0
- /package/dist/{doctor-deep-EN3V4PXO.js.map → composite-router-YPRWVTRB.js.map} +0 -0
- /package/dist/{setup-command-ZN7DSP53.js.map → consensus-vote-DBE6RNZG.js.map} +0 -0
- /package/dist/{setup-config-ZOJUDXNP.js.map → doctor-deep-AWE7SRU6.js.map} +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
BACKOFF_CONFIG,
|
|
3
2
|
CLI_SUBPROCESS_TIMEOUTS,
|
|
4
3
|
CLI_TIMEOUTS,
|
|
5
4
|
CLI_VERSION_REQUIREMENTS,
|
|
@@ -8,13 +7,9 @@ import {
|
|
|
8
7
|
DEFAULT_MODEL_CAPABILITIES,
|
|
9
8
|
DEFAULT_MODEL_PER_CLI,
|
|
10
9
|
ErrorCode,
|
|
11
|
-
LEARNING_DIR,
|
|
12
10
|
NexusError,
|
|
13
|
-
OUTCOMES_FILE,
|
|
14
|
-
RULES_FILE,
|
|
15
11
|
buildModelInfo,
|
|
16
12
|
clampPercent,
|
|
17
|
-
colors,
|
|
18
13
|
createLogger,
|
|
19
14
|
detectTaskCategory,
|
|
20
15
|
err,
|
|
@@ -25,36 +20,17 @@ import {
|
|
|
25
20
|
getOutcomeStore,
|
|
26
21
|
getRandomProvider,
|
|
27
22
|
getTimeProvider,
|
|
28
|
-
|
|
29
|
-
ok
|
|
30
|
-
|
|
31
|
-
writeLine
|
|
32
|
-
} from "./chunk-WF5CWL2N.js";
|
|
23
|
+
isRateLimitText,
|
|
24
|
+
ok
|
|
25
|
+
} from "./chunk-IMWYKX4H.js";
|
|
33
26
|
|
|
34
|
-
// src/
|
|
35
|
-
|
|
27
|
+
// src/cli-adapters/subprocess-adapter.ts
|
|
28
|
+
import { spawn } from "child_process";
|
|
36
29
|
|
|
37
|
-
// src/
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
var delay = sleep;
|
|
42
|
-
async function withTimeout(promise, timeoutMs, errorMessage) {
|
|
43
|
-
let timeoutId;
|
|
44
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
45
|
-
timeoutId = setTimeout(() => {
|
|
46
|
-
reject(new Error(errorMessage));
|
|
47
|
-
}, timeoutMs);
|
|
48
|
-
});
|
|
49
|
-
try {
|
|
50
|
-
const result = await Promise.race([promise, timeoutPromise]);
|
|
51
|
-
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
52
|
-
return { ok: true, value: result };
|
|
53
|
-
} catch (error) {
|
|
54
|
-
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
55
|
-
return { ok: false, error: getErrorMessage(error) };
|
|
56
|
-
}
|
|
57
|
-
}
|
|
30
|
+
// src/cli-adapters/base-adapter.ts
|
|
31
|
+
import { exec } from "child_process";
|
|
32
|
+
import { promisify } from "util";
|
|
33
|
+
import semver from "semver";
|
|
58
34
|
|
|
59
35
|
// src/cli-adapters/cli-timeout-helpers.ts
|
|
60
36
|
function estimateTaskComplexity(taskDescription) {
|
|
@@ -123,11 +99,6 @@ function getTimeoutForTaskAuto(cli, taskDescription) {
|
|
|
123
99
|
return getAdaptiveTimeout(cli, taskDescription);
|
|
124
100
|
}
|
|
125
101
|
|
|
126
|
-
// src/cli-adapters/base-adapter.ts
|
|
127
|
-
import { exec } from "child_process";
|
|
128
|
-
import { promisify } from "util";
|
|
129
|
-
import semver from "semver";
|
|
130
|
-
|
|
131
102
|
// src/cli-adapters/capacity-tracker.ts
|
|
132
103
|
var DEFAULT_TOKEN_LIMITS = {
|
|
133
104
|
claude: 1e5,
|
|
@@ -258,6 +229,98 @@ function createCapacityTracker(cli) {
|
|
|
258
229
|
return new CapacityTracker(getDefaultConfig(cli));
|
|
259
230
|
}
|
|
260
231
|
|
|
232
|
+
// src/utils/async-utils.ts
|
|
233
|
+
function sleep(ms) {
|
|
234
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
235
|
+
}
|
|
236
|
+
var delay = sleep;
|
|
237
|
+
async function withTimeout(promise, timeoutMs, errorMessage) {
|
|
238
|
+
let timeoutId;
|
|
239
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
240
|
+
timeoutId = setTimeout(() => {
|
|
241
|
+
reject(new Error(errorMessage));
|
|
242
|
+
}, timeoutMs);
|
|
243
|
+
});
|
|
244
|
+
try {
|
|
245
|
+
const result = await Promise.race([promise, timeoutPromise]);
|
|
246
|
+
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
247
|
+
return { ok: true, value: result };
|
|
248
|
+
} catch (error) {
|
|
249
|
+
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
250
|
+
return { ok: false, error: getErrorMessage(error) };
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/cli-adapters/cli-retry-loop.ts
|
|
255
|
+
var RETRYABLE_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
256
|
+
"TIMEOUT",
|
|
257
|
+
"RATE_LIMITED",
|
|
258
|
+
"CONNECTION_ERROR"
|
|
259
|
+
]);
|
|
260
|
+
function calculateBackoffDelay(attempt, baseDelayMs, maxDelayMs) {
|
|
261
|
+
const exponentialDelay = baseDelayMs * Math.pow(2, attempt - 1);
|
|
262
|
+
const jitter = getRandomProvider().random() * 0.3 * exponentialDelay;
|
|
263
|
+
const delayMs = exponentialDelay + jitter;
|
|
264
|
+
return Math.min(delayMs, maxDelayMs);
|
|
265
|
+
}
|
|
266
|
+
function isRetryableError(code) {
|
|
267
|
+
return RETRYABLE_ERROR_CODES.has(code);
|
|
268
|
+
}
|
|
269
|
+
function categorizeError(error) {
|
|
270
|
+
switch (error.code) {
|
|
271
|
+
case "TIMEOUT":
|
|
272
|
+
return "timeout";
|
|
273
|
+
case "RATE_LIMITED":
|
|
274
|
+
return "rate_limit";
|
|
275
|
+
case "NOT_AUTHENTICATED":
|
|
276
|
+
return "authentication";
|
|
277
|
+
case "CONNECTION_ERROR":
|
|
278
|
+
return "connection";
|
|
279
|
+
default:
|
|
280
|
+
return "unknown";
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
async function executeCliRetryLoop(executeFn, config) {
|
|
284
|
+
const maxAttempts = config.allowRetry ? config.maxRetries + 1 : 1;
|
|
285
|
+
let lastError;
|
|
286
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
287
|
+
const result = await executeFn();
|
|
288
|
+
if (result.ok) {
|
|
289
|
+
return ok({ response: result.value, retryCount: attempt - 1 });
|
|
290
|
+
}
|
|
291
|
+
lastError = result.error;
|
|
292
|
+
if (config.circuitBreaker !== void 0 && config.circuitBreaker !== null) {
|
|
293
|
+
config.circuitBreaker.recordFailure(categorizeError(lastError));
|
|
294
|
+
}
|
|
295
|
+
if (!shouldRetry(lastError, attempt, maxAttempts, config.circuitBreaker)) {
|
|
296
|
+
return err(lastError);
|
|
297
|
+
}
|
|
298
|
+
const delayMs = calculateBackoffDelay(attempt, config.baseDelayMs, config.maxDelayMs);
|
|
299
|
+
config.logger.debug("Retrying CLI execution", {
|
|
300
|
+
cli: config.cli,
|
|
301
|
+
attempt,
|
|
302
|
+
nextAttempt: attempt + 1,
|
|
303
|
+
delayMs: Math.round(delayMs)
|
|
304
|
+
});
|
|
305
|
+
await delay(delayMs);
|
|
306
|
+
}
|
|
307
|
+
return err(
|
|
308
|
+
lastError ?? {
|
|
309
|
+
code: "UNKNOWN",
|
|
310
|
+
message: "Max retries exceeded",
|
|
311
|
+
cli: config.cli,
|
|
312
|
+
retryable: false
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
function shouldRetry(error, attempt, maxAttempts, circuitBreaker) {
|
|
317
|
+
if (attempt >= maxAttempts) return false;
|
|
318
|
+
if (!error.retryable) return false;
|
|
319
|
+
if (!isRetryableError(error.code)) return false;
|
|
320
|
+
if (circuitBreaker?.getState() === "open") return false;
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
|
|
261
324
|
// src/cli-adapters/base-adapter.ts
|
|
262
325
|
var execAsync = promisify(exec);
|
|
263
326
|
var DEFAULT_CAPACITY_FALLBACK = 1e5;
|
|
@@ -276,8 +339,8 @@ var BaseCliAdapter = class {
|
|
|
276
339
|
initialized = false;
|
|
277
340
|
lastHealthCheck;
|
|
278
341
|
cachedVersion;
|
|
279
|
-
constructor(
|
|
280
|
-
this.logger =
|
|
342
|
+
constructor(logger5) {
|
|
343
|
+
this.logger = logger5 ?? createLogger({ component: "cli-adapter" });
|
|
281
344
|
}
|
|
282
345
|
/**
|
|
283
346
|
* Initializes the capacity tracker.
|
|
@@ -323,47 +386,33 @@ var BaseCliAdapter = class {
|
|
|
323
386
|
return getTimeoutForTaskAuto(this.name, task.content);
|
|
324
387
|
}
|
|
325
388
|
/**
|
|
326
|
-
* Executes task with retry logic.
|
|
389
|
+
* Executes task with retry logic via shared retry loop.
|
|
327
390
|
*/
|
|
328
391
|
async executeWithRetry(task, opts) {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
});
|
|
341
|
-
return result;
|
|
342
|
-
}
|
|
343
|
-
lastError = result.error;
|
|
344
|
-
if (this.isTerminalAttempt(result.error, attempt, maxAttempts)) {
|
|
345
|
-
this.logger.warn("Task execution failed", {
|
|
346
|
-
cli: this.name,
|
|
347
|
-
attempt,
|
|
348
|
-
error: result.error.message,
|
|
349
|
-
retryable: result.error.retryable
|
|
350
|
-
});
|
|
351
|
-
return result;
|
|
352
|
-
}
|
|
353
|
-
this.logger.debug("Retrying task execution", {
|
|
392
|
+
const result = await executeCliRetryLoop(() => this.executeTask(task, opts), {
|
|
393
|
+
maxRetries: opts.maxRetries,
|
|
394
|
+
allowRetry: opts.allowRetry,
|
|
395
|
+
baseDelayMs: 1e3,
|
|
396
|
+
maxDelayMs: 16e3,
|
|
397
|
+
cli: this.name,
|
|
398
|
+
logger: this.logger
|
|
399
|
+
});
|
|
400
|
+
if (result.ok) {
|
|
401
|
+
this.recordUsage(result.value.response);
|
|
402
|
+
this.logger.info("Task executed successfully", {
|
|
354
403
|
cli: this.name,
|
|
355
|
-
attempt,
|
|
356
|
-
|
|
404
|
+
attempt: result.value.retryCount + 1,
|
|
405
|
+
durationMs: result.value.response.durationMs,
|
|
406
|
+
tokensUsed: result.value.response.usage?.totalTokens
|
|
357
407
|
});
|
|
358
|
-
|
|
408
|
+
return { ok: true, value: result.value.response };
|
|
359
409
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
return !error.retryable || attempt === maxAttempts;
|
|
410
|
+
this.logger.warn("Task execution failed", {
|
|
411
|
+
cli: this.name,
|
|
412
|
+
error: result.error.message,
|
|
413
|
+
retryable: result.error.retryable
|
|
414
|
+
});
|
|
415
|
+
return result;
|
|
367
416
|
}
|
|
368
417
|
/**
|
|
369
418
|
* Performs a health check.
|
|
@@ -518,19 +567,39 @@ var BaseCliAdapter = class {
|
|
|
518
567
|
}
|
|
519
568
|
};
|
|
520
569
|
|
|
521
|
-
// src/
|
|
522
|
-
|
|
523
|
-
var
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
570
|
+
// src/security/output-sanitizer.ts
|
|
571
|
+
var REDACTED_KEY_PLACEHOLDER = "[REDACTED_KEY]";
|
|
572
|
+
var KEY_PATTERNS = [
|
|
573
|
+
// Anthropic: sk-ant-api03-... (at least 20 chars after prefix)
|
|
574
|
+
/sk-ant-[A-Za-z0-9_-]{20,}/g,
|
|
575
|
+
// OpenAI project: sk-proj-... (at least 20 chars after prefix)
|
|
576
|
+
/sk-proj-[A-Za-z0-9_-]{20,}/g,
|
|
577
|
+
// Generic OpenAI: sk-... (at least 20 chars after prefix)
|
|
578
|
+
/sk-[A-Za-z0-9_-]{20,}/g,
|
|
579
|
+
// Google AI / Gemini: AIzaSy... (at least 30 chars total)
|
|
580
|
+
/AIzaSy[A-Za-z0-9_-]{24,}/g,
|
|
581
|
+
// GitHub PAT: ghp_...
|
|
582
|
+
/ghp_[A-Za-z0-9]{20,}/g,
|
|
583
|
+
// GitHub OAuth: gho_...
|
|
584
|
+
/gho_[A-Za-z0-9]{20,}/g,
|
|
585
|
+
// GitLab PAT: glpat-...
|
|
586
|
+
/glpat-[A-Za-z0-9_-]{10,}/g,
|
|
587
|
+
// npm token: npm_...
|
|
588
|
+
/npm_[A-Za-z0-9]{20,}/g,
|
|
589
|
+
// PyPI token: pypi-...
|
|
590
|
+
/pypi-[A-Za-z0-9_-]{20,}/g
|
|
529
591
|
];
|
|
530
|
-
function
|
|
531
|
-
|
|
532
|
-
|
|
592
|
+
function sanitizeOutput(text) {
|
|
593
|
+
if (text === "") return text;
|
|
594
|
+
let result = text;
|
|
595
|
+
for (const pattern of KEY_PATTERNS) {
|
|
596
|
+
pattern.lastIndex = 0;
|
|
597
|
+
result = result.replace(pattern, REDACTED_KEY_PLACEHOLDER);
|
|
598
|
+
}
|
|
599
|
+
return result;
|
|
533
600
|
}
|
|
601
|
+
|
|
602
|
+
// src/cli-adapters/subprocess-adapter.ts
|
|
534
603
|
var PLAINTEXT_FALLBACK_MIN_LENGTH = 30;
|
|
535
604
|
function tryPlaintextFallback(stdout) {
|
|
536
605
|
const trimmed = stdout.trim();
|
|
@@ -572,12 +641,11 @@ var STDERR_CONNECTION_PATTERNS = [
|
|
|
572
641
|
"eaddrinuse",
|
|
573
642
|
"address already in use"
|
|
574
643
|
];
|
|
575
|
-
var STDERR_RATE_LIMIT_PATTERNS = ["rate limit", "quota exceeded", "429", "too many requests"];
|
|
576
644
|
var STDERR_TIMEOUT_PATTERNS = ["timeout", "timed out", "etimedout"];
|
|
577
645
|
function classifyStderrError(stderr) {
|
|
578
646
|
const lower = stderr.toLowerCase();
|
|
579
647
|
if (STDERR_CONNECTION_PATTERNS.some((p) => lower.includes(p))) return "CONNECTION_ERROR";
|
|
580
|
-
if (
|
|
648
|
+
if (isRateLimitText(stderr)) return "RATE_LIMITED";
|
|
581
649
|
if (STDERR_TIMEOUT_PATTERNS.some((p) => lower.includes(p))) return "TIMEOUT";
|
|
582
650
|
return "EXECUTION_ERROR";
|
|
583
651
|
}
|
|
@@ -731,6 +799,8 @@ var SubprocessCliAdapter = class extends BaseCliAdapter {
|
|
|
731
799
|
}
|
|
732
800
|
/** Classify a subprocess close event into a Result. */
|
|
733
801
|
classifyCloseResult(code, state, startTime) {
|
|
802
|
+
state.stdout = sanitizeOutput(state.stdout);
|
|
803
|
+
state.stderr = sanitizeOutput(state.stderr);
|
|
734
804
|
if (code !== 0 && state.stdout === "") {
|
|
735
805
|
if (state.stderr !== "") {
|
|
736
806
|
const msg2 = state.stderr;
|
|
@@ -757,7 +827,7 @@ var SubprocessCliAdapter = class extends BaseCliAdapter {
|
|
|
757
827
|
}
|
|
758
828
|
const text = this.parser.extractResponse(stdout);
|
|
759
829
|
if (text === null) {
|
|
760
|
-
if (
|
|
830
|
+
if (isRateLimitText(stdout)) {
|
|
761
831
|
const snippet2 = stdout.slice(0, 500).trim();
|
|
762
832
|
return err(this.createError("RATE_LIMITED", snippet2));
|
|
763
833
|
}
|
|
@@ -800,7 +870,7 @@ var SubprocessCliAdapter = class extends BaseCliAdapter {
|
|
|
800
870
|
if (error.message.includes("ENOENT")) {
|
|
801
871
|
return err(this.createError("NOT_FOUND", `${this.name} CLI not found`, error));
|
|
802
872
|
}
|
|
803
|
-
return err(this.createError("EXECUTION_ERROR", error.message, error));
|
|
873
|
+
return err(this.createError("EXECUTION_ERROR", sanitizeOutput(error.message), error));
|
|
804
874
|
}
|
|
805
875
|
/**
|
|
806
876
|
* Initializes the adapter and capacity tracker.
|
|
@@ -1352,7 +1422,7 @@ var DEFAULT_CIRCUIT_BREAKER_CONFIG = {
|
|
|
1352
1422
|
};
|
|
1353
1423
|
var TIMEOUT_PATTERNS = ["timeout", "timed out"];
|
|
1354
1424
|
var AUTH_PATTERNS = ["auth", "unauthorized", "forbidden", "oauth"];
|
|
1355
|
-
var
|
|
1425
|
+
var RATE_LIMIT_PATTERNS = ["rate limit", "too many requests", "429"];
|
|
1356
1426
|
var CONNECTION_PATTERNS = [
|
|
1357
1427
|
"connection",
|
|
1358
1428
|
"econnrefused",
|
|
@@ -1365,7 +1435,7 @@ var CRASH_PATTERNS = ["crash", "exited", "killed", "sigterm", "sigkill"];
|
|
|
1365
1435
|
function matchesPatterns(text, patterns) {
|
|
1366
1436
|
return patterns.some((pattern) => text.includes(pattern));
|
|
1367
1437
|
}
|
|
1368
|
-
function
|
|
1438
|
+
function categorizeError2(error) {
|
|
1369
1439
|
if (!(error instanceof Error)) {
|
|
1370
1440
|
return "unknown";
|
|
1371
1441
|
}
|
|
@@ -1374,7 +1444,7 @@ function categorizeError(error) {
|
|
|
1374
1444
|
const combined = `${message} ${name}`;
|
|
1375
1445
|
if (matchesPatterns(combined, TIMEOUT_PATTERNS)) return "timeout";
|
|
1376
1446
|
if (matchesPatterns(combined, AUTH_PATTERNS)) return "authentication";
|
|
1377
|
-
if (matchesPatterns(combined,
|
|
1447
|
+
if (matchesPatterns(combined, RATE_LIMIT_PATTERNS)) return "rate_limit";
|
|
1378
1448
|
if (matchesPatterns(combined, CONNECTION_PATTERNS)) return "connection";
|
|
1379
1449
|
if (matchesPatterns(combined, CRASH_PATTERNS)) return "crash";
|
|
1380
1450
|
return "unknown";
|
|
@@ -1407,7 +1477,7 @@ var CliCircuitBreaker = class {
|
|
|
1407
1477
|
this.onSuccess();
|
|
1408
1478
|
return ok(result);
|
|
1409
1479
|
} catch (error) {
|
|
1410
|
-
const category =
|
|
1480
|
+
const category = categorizeError2(error);
|
|
1411
1481
|
if (this.shouldCountFailure(category)) {
|
|
1412
1482
|
this.onFailure(category);
|
|
1413
1483
|
}
|
|
@@ -1654,34 +1724,6 @@ var GEMINI_LEGACY_DEFAULTS = {
|
|
|
1654
1724
|
inputCost: 0.15,
|
|
1655
1725
|
outputCost: 0.6
|
|
1656
1726
|
};
|
|
1657
|
-
var RETRYABLE_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
1658
|
-
"TIMEOUT",
|
|
1659
|
-
"RATE_LIMITED",
|
|
1660
|
-
"CONNECTION_ERROR"
|
|
1661
|
-
]);
|
|
1662
|
-
function calculateBackoffDelay(attempt, baseDelayMs, maxDelayMs) {
|
|
1663
|
-
const exponentialDelay = baseDelayMs * Math.pow(2, attempt - 1);
|
|
1664
|
-
const jitter = getRandomProvider().random() * 0.3 * exponentialDelay;
|
|
1665
|
-
const delay2 = exponentialDelay + jitter;
|
|
1666
|
-
return Math.min(delay2, maxDelayMs);
|
|
1667
|
-
}
|
|
1668
|
-
function isRetryableError(code) {
|
|
1669
|
-
return RETRYABLE_ERROR_CODES.has(code);
|
|
1670
|
-
}
|
|
1671
|
-
function categorizeError2(error) {
|
|
1672
|
-
switch (error.code) {
|
|
1673
|
-
case "TIMEOUT":
|
|
1674
|
-
return "timeout";
|
|
1675
|
-
case "RATE_LIMITED":
|
|
1676
|
-
return "rate_limit";
|
|
1677
|
-
case "NOT_AUTHENTICATED":
|
|
1678
|
-
return "authentication";
|
|
1679
|
-
case "CONNECTION_ERROR":
|
|
1680
|
-
return "connection";
|
|
1681
|
-
default:
|
|
1682
|
-
return "unknown";
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1685
1727
|
function createCircuitOpenError(cli) {
|
|
1686
1728
|
return {
|
|
1687
1729
|
code: "EXECUTION_ERROR",
|
|
@@ -1833,69 +1875,14 @@ var GeminiCliAdapter = class extends SubprocessCliAdapter {
|
|
|
1833
1875
|
return err(result.error);
|
|
1834
1876
|
}
|
|
1835
1877
|
async executeWithRetryTracking(task, options) {
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
this.logRetryAttempt(retryContext, task);
|
|
1845
|
-
const result = await this.executeTask(task, options);
|
|
1846
|
-
if (result.ok) {
|
|
1847
|
-
return ok({ response: result.value, retryCount: retryContext.attempt - 1 });
|
|
1848
|
-
}
|
|
1849
|
-
const error = result.error;
|
|
1850
|
-
retryContext = { ...retryContext, lastError: error };
|
|
1851
|
-
if (this.circuitBreaker !== null) {
|
|
1852
|
-
this.circuitBreaker.recordFailure(categorizeError2(error));
|
|
1853
|
-
}
|
|
1854
|
-
if (!this.shouldRetry(error, retryContext)) {
|
|
1855
|
-
return err(error);
|
|
1856
|
-
}
|
|
1857
|
-
const delayMs = calculateBackoffDelay(
|
|
1858
|
-
retryContext.attempt,
|
|
1859
|
-
this.baseDelayMs,
|
|
1860
|
-
this.maxDelayMs
|
|
1861
|
-
);
|
|
1862
|
-
retryContext = { ...retryContext, totalDelayMs: retryContext.totalDelayMs + delayMs };
|
|
1863
|
-
this.logRetryDelay(retryContext, delayMs);
|
|
1864
|
-
await delay(delayMs);
|
|
1865
|
-
}
|
|
1866
|
-
return err(retryContext.lastError ?? this.createError("UNKNOWN", "Max retries exceeded"));
|
|
1867
|
-
}
|
|
1868
|
-
shouldRetry(error, context) {
|
|
1869
|
-
if (context.attempt >= context.maxAttempts) {
|
|
1870
|
-
return false;
|
|
1871
|
-
}
|
|
1872
|
-
if (!error.retryable) {
|
|
1873
|
-
return false;
|
|
1874
|
-
}
|
|
1875
|
-
if (this.circuitBreaker?.getState() === "open") {
|
|
1876
|
-
return false;
|
|
1877
|
-
}
|
|
1878
|
-
return isRetryableError(error.code);
|
|
1879
|
-
}
|
|
1880
|
-
logRetryAttempt(context, task) {
|
|
1881
|
-
if (context.attempt === 1) {
|
|
1882
|
-
this.adapterLogger.debug("Executing Gemini task", {
|
|
1883
|
-
contentLength: task.content.length,
|
|
1884
|
-
model: task.model ?? this.model
|
|
1885
|
-
});
|
|
1886
|
-
} else {
|
|
1887
|
-
this.adapterLogger.info("Retrying Gemini task", {
|
|
1888
|
-
attempt: context.attempt,
|
|
1889
|
-
maxAttempts: context.maxAttempts,
|
|
1890
|
-
lastError: context.lastError?.code
|
|
1891
|
-
});
|
|
1892
|
-
}
|
|
1893
|
-
}
|
|
1894
|
-
logRetryDelay(context, delayMs) {
|
|
1895
|
-
this.adapterLogger.debug("Backoff delay before retry", {
|
|
1896
|
-
attempt: context.attempt,
|
|
1897
|
-
delayMs: Math.round(delayMs),
|
|
1898
|
-
totalDelayMs: Math.round(context.totalDelayMs)
|
|
1878
|
+
return executeCliRetryLoop(() => this.executeTask(task, options), {
|
|
1879
|
+
maxRetries: options.maxRetries,
|
|
1880
|
+
allowRetry: options.allowRetry,
|
|
1881
|
+
baseDelayMs: this.baseDelayMs,
|
|
1882
|
+
maxDelayMs: this.maxDelayMs,
|
|
1883
|
+
circuitBreaker: this.circuitBreaker,
|
|
1884
|
+
cli: this.name,
|
|
1885
|
+
logger: this.adapterLogger
|
|
1899
1886
|
});
|
|
1900
1887
|
}
|
|
1901
1888
|
};
|
|
@@ -2933,9 +2920,9 @@ function createCodexAdapter(transport, options) {
|
|
|
2933
2920
|
}
|
|
2934
2921
|
return new CodexMcpAdapter(options);
|
|
2935
2922
|
}
|
|
2936
|
-
function createAllAdapters(
|
|
2923
|
+
function createAllAdapters(logger5, codexTransport = "mcp") {
|
|
2937
2924
|
const adapters = /* @__PURE__ */ new Map();
|
|
2938
|
-
const options =
|
|
2925
|
+
const options = logger5 !== void 0 ? { logger: logger5 } : void 0;
|
|
2939
2926
|
adapters.set("claude", new ClaudeCliAdapter(options));
|
|
2940
2927
|
adapters.set("gemini", new GeminiCliAdapter(options));
|
|
2941
2928
|
adapters.set("codex", createCodexAdapter(codexTransport, options ?? {}));
|
|
@@ -2979,919 +2966,7 @@ async function getAvailableClis(cache) {
|
|
|
2979
2966
|
).map((r) => r.value.cli);
|
|
2980
2967
|
}
|
|
2981
2968
|
|
|
2982
|
-
// src/cli/setup-data-dir.ts
|
|
2983
|
-
import { mkdirSync, existsSync as existsSync2 } from "fs";
|
|
2984
|
-
import { homedir as homedir2 } from "os";
|
|
2985
|
-
import { join as join2 } from "path";
|
|
2986
|
-
|
|
2987
|
-
// src/cli/doctor.ts
|
|
2988
|
-
import { existsSync, readFileSync, accessSync, constants as fsConstants } from "fs";
|
|
2989
|
-
import { homedir } from "os";
|
|
2990
|
-
import { join } from "path";
|
|
2991
|
-
|
|
2992
|
-
// src/mcp/server.ts
|
|
2993
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2994
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2995
|
-
|
|
2996
|
-
// src/mcp/task-store.ts
|
|
2997
|
-
import { InMemoryTaskStore } from "@modelcontextprotocol/sdk/experimental/tasks";
|
|
2998
|
-
var MAX_TASK_TTL_MS = 6e5;
|
|
2999
|
-
var DEFAULT_TASK_TTL_MS = 3e5;
|
|
3000
|
-
var MAX_TASK_CAPACITY = 50;
|
|
3001
|
-
var CLEANUP_INTERVAL_MS = 6e4;
|
|
3002
|
-
var logger5 = createLogger({ component: "task-store" });
|
|
3003
|
-
var singletonStore;
|
|
3004
|
-
var cleanupTimer;
|
|
3005
|
-
function getTaskStore() {
|
|
3006
|
-
if (singletonStore === void 0) {
|
|
3007
|
-
singletonStore = new InMemoryTaskStore();
|
|
3008
|
-
logger5.info("Task store created", { maxCapacity: MAX_TASK_CAPACITY });
|
|
3009
|
-
cleanupTimer = setInterval(() => {
|
|
3010
|
-
evictExcessTasks();
|
|
3011
|
-
}, CLEANUP_INTERVAL_MS);
|
|
3012
|
-
if (typeof cleanupTimer === "object" && "unref" in cleanupTimer) {
|
|
3013
|
-
cleanupTimer.unref();
|
|
3014
|
-
}
|
|
3015
|
-
}
|
|
3016
|
-
return singletonStore;
|
|
3017
|
-
}
|
|
3018
|
-
function clampTaskTtl(requestedTtl) {
|
|
3019
|
-
if (requestedTtl === void 0 || requestedTtl === null) {
|
|
3020
|
-
return DEFAULT_TASK_TTL_MS;
|
|
3021
|
-
}
|
|
3022
|
-
if (requestedTtl > MAX_TASK_TTL_MS) {
|
|
3023
|
-
logger5.warn("Task TTL clamped to maximum", {
|
|
3024
|
-
requested: requestedTtl,
|
|
3025
|
-
max: MAX_TASK_TTL_MS
|
|
3026
|
-
});
|
|
3027
|
-
return MAX_TASK_TTL_MS;
|
|
3028
|
-
}
|
|
3029
|
-
return requestedTtl;
|
|
3030
|
-
}
|
|
3031
|
-
function evictExcessTasks() {
|
|
3032
|
-
if (singletonStore === void 0) return;
|
|
3033
|
-
const allTasks = singletonStore.getAllTasks();
|
|
3034
|
-
if (allTasks.length <= MAX_TASK_CAPACITY) return;
|
|
3035
|
-
const sorted = [...allTasks].sort(
|
|
3036
|
-
(a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
|
|
3037
|
-
);
|
|
3038
|
-
const evictCount = sorted.length - MAX_TASK_CAPACITY;
|
|
3039
|
-
for (let i = 0; i < evictCount; i++) {
|
|
3040
|
-
const task = sorted[i];
|
|
3041
|
-
if (task === void 0) continue;
|
|
3042
|
-
singletonStore.updateTaskStatus(task.taskId, "cancelled", "Evicted: capacity exceeded").catch((err2) => {
|
|
3043
|
-
logger5.debug("Failed to evict task", {
|
|
3044
|
-
taskId: task.taskId,
|
|
3045
|
-
error: String(err2)
|
|
3046
|
-
});
|
|
3047
|
-
});
|
|
3048
|
-
}
|
|
3049
|
-
logger5.info("Evicted excess tasks", { evicted: evictCount, total: sorted.length });
|
|
3050
|
-
}
|
|
3051
|
-
|
|
3052
|
-
// src/mcp/server.ts
|
|
3053
|
-
var DEFAULT_SERVER_NAME = "nexus-agents";
|
|
3054
|
-
function createServerError(code, message, error) {
|
|
3055
|
-
const serverError = { code, message };
|
|
3056
|
-
if (error instanceof Error) {
|
|
3057
|
-
serverError.cause = error;
|
|
3058
|
-
}
|
|
3059
|
-
return serverError;
|
|
3060
|
-
}
|
|
3061
|
-
function createServer(config) {
|
|
3062
|
-
const serverName = config?.name ?? DEFAULT_SERVER_NAME;
|
|
3063
|
-
const serverVersion = config?.version ?? VERSION;
|
|
3064
|
-
const logger6 = config?.logger ?? createLogger({ component: "mcp-server" });
|
|
3065
|
-
try {
|
|
3066
|
-
initDataDirectories();
|
|
3067
|
-
logger6.info("Creating MCP server", {
|
|
3068
|
-
name: serverName,
|
|
3069
|
-
version: serverVersion
|
|
3070
|
-
});
|
|
3071
|
-
const server = new McpServer(
|
|
3072
|
-
{
|
|
3073
|
-
name: serverName,
|
|
3074
|
-
version: serverVersion
|
|
3075
|
-
},
|
|
3076
|
-
{
|
|
3077
|
-
capabilities: {
|
|
3078
|
-
logging: {},
|
|
3079
|
-
prompts: {},
|
|
3080
|
-
resources: {},
|
|
3081
|
-
tasks: {}
|
|
3082
|
-
},
|
|
3083
|
-
taskStore: getTaskStore()
|
|
3084
|
-
}
|
|
3085
|
-
);
|
|
3086
|
-
logger6.debug("MCP server created successfully");
|
|
3087
|
-
return ok({ server, logger: logger6 });
|
|
3088
|
-
} catch (error) {
|
|
3089
|
-
const errorMessage = getErrorMessage(error);
|
|
3090
|
-
logger6.error("Failed to create MCP server", error instanceof Error ? error : void 0);
|
|
3091
|
-
return err(
|
|
3092
|
-
createServerError(
|
|
3093
|
-
"SERVER_CREATION_FAILED",
|
|
3094
|
-
`Failed to create MCP server: ${errorMessage}`,
|
|
3095
|
-
error
|
|
3096
|
-
)
|
|
3097
|
-
);
|
|
3098
|
-
}
|
|
3099
|
-
}
|
|
3100
|
-
async function connectTransport(server, transport, logger6) {
|
|
3101
|
-
const log = logger6 ?? createLogger({ component: "mcp-server" });
|
|
3102
|
-
try {
|
|
3103
|
-
log.info("Connecting server to transport");
|
|
3104
|
-
await server.connect(transport);
|
|
3105
|
-
log.debug("Server connected to transport successfully");
|
|
3106
|
-
return ok(void 0);
|
|
3107
|
-
} catch (error) {
|
|
3108
|
-
const errorMessage = getErrorMessage(error);
|
|
3109
|
-
log.error("Failed to connect to transport", error instanceof Error ? error : void 0);
|
|
3110
|
-
return err(
|
|
3111
|
-
createServerError(
|
|
3112
|
-
"SERVER_START_FAILED",
|
|
3113
|
-
`Failed to connect to transport: ${errorMessage}`,
|
|
3114
|
-
error
|
|
3115
|
-
)
|
|
3116
|
-
);
|
|
3117
|
-
}
|
|
3118
|
-
}
|
|
3119
|
-
async function startStdioServer(config) {
|
|
3120
|
-
const serverResult = createServer(config);
|
|
3121
|
-
if (!serverResult.ok) {
|
|
3122
|
-
return serverResult;
|
|
3123
|
-
}
|
|
3124
|
-
const { server, logger: logger6 } = serverResult.value;
|
|
3125
|
-
try {
|
|
3126
|
-
logger6.info("Starting stdio transport");
|
|
3127
|
-
const transport = new StdioServerTransport();
|
|
3128
|
-
const connectResult = await connectTransport(server, transport, logger6);
|
|
3129
|
-
if (!connectResult.ok) {
|
|
3130
|
-
return connectResult;
|
|
3131
|
-
}
|
|
3132
|
-
logger6.info("MCP server running with stdio transport");
|
|
3133
|
-
return ok({ server, logger: logger6 });
|
|
3134
|
-
} catch (error) {
|
|
3135
|
-
const errorMessage = getErrorMessage(error);
|
|
3136
|
-
logger6.error("Failed to start stdio server", error instanceof Error ? error : void 0);
|
|
3137
|
-
return err(
|
|
3138
|
-
createServerError(
|
|
3139
|
-
"SERVER_START_FAILED",
|
|
3140
|
-
`Failed to start stdio server: ${errorMessage}`,
|
|
3141
|
-
error
|
|
3142
|
-
)
|
|
3143
|
-
);
|
|
3144
|
-
}
|
|
3145
|
-
}
|
|
3146
|
-
async function closeServer(server, logger6) {
|
|
3147
|
-
const log = logger6 ?? createLogger({ component: "mcp-server" });
|
|
3148
|
-
try {
|
|
3149
|
-
log.info("Closing MCP server");
|
|
3150
|
-
await server.close();
|
|
3151
|
-
log.info("MCP server closed successfully");
|
|
3152
|
-
return ok(void 0);
|
|
3153
|
-
} catch (error) {
|
|
3154
|
-
const errorMessage = getErrorMessage(error);
|
|
3155
|
-
log.error("Failed to close server", error instanceof Error ? error : void 0);
|
|
3156
|
-
return err(
|
|
3157
|
-
createServerError("SERVER_STOP_FAILED", `Failed to close server: ${errorMessage}`, error)
|
|
3158
|
-
);
|
|
3159
|
-
}
|
|
3160
|
-
}
|
|
3161
|
-
|
|
3162
|
-
// src/utils/text-utils.ts
|
|
3163
|
-
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
3164
|
-
"a",
|
|
3165
|
-
"an",
|
|
3166
|
-
"and",
|
|
3167
|
-
"are",
|
|
3168
|
-
"as",
|
|
3169
|
-
"at",
|
|
3170
|
-
"be",
|
|
3171
|
-
"by",
|
|
3172
|
-
"for",
|
|
3173
|
-
"from",
|
|
3174
|
-
"has",
|
|
3175
|
-
"he",
|
|
3176
|
-
"in",
|
|
3177
|
-
"is",
|
|
3178
|
-
"it",
|
|
3179
|
-
"its",
|
|
3180
|
-
"of",
|
|
3181
|
-
"on",
|
|
3182
|
-
"or",
|
|
3183
|
-
"that",
|
|
3184
|
-
"the",
|
|
3185
|
-
"to",
|
|
3186
|
-
"was",
|
|
3187
|
-
"were",
|
|
3188
|
-
"will",
|
|
3189
|
-
"with",
|
|
3190
|
-
"this",
|
|
3191
|
-
"but",
|
|
3192
|
-
"they",
|
|
3193
|
-
"have",
|
|
3194
|
-
"had",
|
|
3195
|
-
"what",
|
|
3196
|
-
"when",
|
|
3197
|
-
"where",
|
|
3198
|
-
"who",
|
|
3199
|
-
"which",
|
|
3200
|
-
"why",
|
|
3201
|
-
"how",
|
|
3202
|
-
"all",
|
|
3203
|
-
"each",
|
|
3204
|
-
"every",
|
|
3205
|
-
"both",
|
|
3206
|
-
"few",
|
|
3207
|
-
"more",
|
|
3208
|
-
"most",
|
|
3209
|
-
"other",
|
|
3210
|
-
"some",
|
|
3211
|
-
"such",
|
|
3212
|
-
"no",
|
|
3213
|
-
"nor",
|
|
3214
|
-
"not",
|
|
3215
|
-
"only",
|
|
3216
|
-
"own",
|
|
3217
|
-
"same",
|
|
3218
|
-
"so",
|
|
3219
|
-
"than",
|
|
3220
|
-
"too",
|
|
3221
|
-
"very",
|
|
3222
|
-
"can",
|
|
3223
|
-
"just",
|
|
3224
|
-
"should",
|
|
3225
|
-
"now",
|
|
3226
|
-
"i",
|
|
3227
|
-
"you",
|
|
3228
|
-
"we",
|
|
3229
|
-
"me",
|
|
3230
|
-
"my",
|
|
3231
|
-
"your",
|
|
3232
|
-
"our",
|
|
3233
|
-
"their",
|
|
3234
|
-
"him",
|
|
3235
|
-
"her",
|
|
3236
|
-
"them",
|
|
3237
|
-
"his",
|
|
3238
|
-
"hers",
|
|
3239
|
-
"able"
|
|
3240
|
-
]);
|
|
3241
|
-
function tokenize(text, minLength = 2) {
|
|
3242
|
-
return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= minLength);
|
|
3243
|
-
}
|
|
3244
|
-
function tokenizeFiltered(text, minLength = 2) {
|
|
3245
|
-
return tokenize(text, minLength).filter((t) => !STOPWORDS.has(t));
|
|
3246
|
-
}
|
|
3247
|
-
function stringifyValue(value) {
|
|
3248
|
-
if (typeof value === "string") return value;
|
|
3249
|
-
if (value === null || value === void 0) return "";
|
|
3250
|
-
return JSON.stringify(value);
|
|
3251
|
-
}
|
|
3252
|
-
function capitalize(str) {
|
|
3253
|
-
if (str.length === 0) return str;
|
|
3254
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
3255
|
-
}
|
|
3256
|
-
function capitalizeWords(str) {
|
|
3257
|
-
return str.split(" ").map((word) => capitalize(word)).join(" ");
|
|
3258
|
-
}
|
|
3259
|
-
function capitalizeKebab(str) {
|
|
3260
|
-
return str.split("-").map((word) => capitalize(word)).join(" ");
|
|
3261
|
-
}
|
|
3262
|
-
function truncateWithInfo(text, maxLength) {
|
|
3263
|
-
if (text.length <= maxLength) return text;
|
|
3264
|
-
const truncatedBytes = text.length - maxLength;
|
|
3265
|
-
return `${text.slice(0, maxLength)}
|
|
3266
|
-
... [truncated ${String(truncatedBytes)} bytes]`;
|
|
3267
|
-
}
|
|
3268
|
-
function truncateSentence(text, maxLength = 150) {
|
|
3269
|
-
const sentenceEnd = text.search(/[.!?](?:\s|$)/);
|
|
3270
|
-
if (sentenceEnd !== -1 && sentenceEnd < maxLength) {
|
|
3271
|
-
return text.slice(0, sentenceEnd + 1).trim();
|
|
3272
|
-
}
|
|
3273
|
-
if (text.length <= maxLength) return text;
|
|
3274
|
-
return text.slice(0, maxLength).trim() + "...";
|
|
3275
|
-
}
|
|
3276
|
-
|
|
3277
|
-
// src/cli/doctor-formatting.ts
|
|
3278
|
-
var REQUIRED_NODE_MAJOR = 22;
|
|
3279
|
-
function formatStatus(healthy, warn = false) {
|
|
3280
|
-
if (healthy) return `${colors.green}${symbols.check}${colors.reset}`;
|
|
3281
|
-
if (warn) return `${colors.yellow}${symbols.warn}${colors.reset}`;
|
|
3282
|
-
return `${colors.red}${symbols.cross}${colors.reset}`;
|
|
3283
|
-
}
|
|
3284
|
-
function formatVersionStatus(status) {
|
|
3285
|
-
switch (status) {
|
|
3286
|
-
case "supported":
|
|
3287
|
-
return `${colors.green}supported${colors.reset}`;
|
|
3288
|
-
case "outdated":
|
|
3289
|
-
return `${colors.yellow}outdated${colors.reset}`;
|
|
3290
|
-
case "unsupported":
|
|
3291
|
-
case "breaking":
|
|
3292
|
-
return `${colors.red}${status}${colors.reset}`;
|
|
3293
|
-
default:
|
|
3294
|
-
return status;
|
|
3295
|
-
}
|
|
3296
|
-
}
|
|
3297
|
-
function formatCapacity(capacity) {
|
|
3298
|
-
if (capacity === void 0) return "Unknown";
|
|
3299
|
-
const remaining = 100 - capacity.utilizationPercent;
|
|
3300
|
-
const remainingStr = String(remaining);
|
|
3301
|
-
if (remaining > 80) return `${colors.green}${remainingStr}% remaining${colors.reset}`;
|
|
3302
|
-
if (remaining > 20) return `${colors.yellow}${remainingStr}% remaining${colors.reset}`;
|
|
3303
|
-
return `${colors.red}${remainingStr}% remaining${colors.reset}`;
|
|
3304
|
-
}
|
|
3305
|
-
function printInstalledCliDetails(cli) {
|
|
3306
|
-
writeLine(` Version: ${cli.version} (${formatVersionStatus(cli.versionStatus)})`);
|
|
3307
|
-
const authText = cli.authenticated ? `${colors.green}${cli.authMethod ?? "Authenticated"}${colors.reset}` : `${colors.red}Not authenticated${colors.reset}`;
|
|
3308
|
-
writeLine(` Auth: ${authText}`);
|
|
3309
|
-
if (cli.capacity !== void 0) {
|
|
3310
|
-
writeLine(` Capacity: ${formatCapacity(cli.capacity)}`);
|
|
3311
|
-
}
|
|
3312
|
-
}
|
|
3313
|
-
function printCliResult(cli) {
|
|
3314
|
-
const status = cli.installed && cli.authenticated;
|
|
3315
|
-
const warn = cli.installed && (!cli.authenticated || cli.versionStatus === "outdated");
|
|
3316
|
-
writeLine(
|
|
3317
|
-
`${formatStatus(status, warn)} ${colors.bold}${capitalize(cli.name)} CLI${colors.reset}`
|
|
3318
|
-
);
|
|
3319
|
-
if (cli.installed) {
|
|
3320
|
-
printInstalledCliDetails(cli);
|
|
3321
|
-
} else {
|
|
3322
|
-
const errorText = cli.error ?? "Not installed";
|
|
3323
|
-
writeLine(` ${colors.red}Error: ${errorText}${colors.reset}`);
|
|
3324
|
-
}
|
|
3325
|
-
if (cli.fix !== void 0 && cli.fix !== "") {
|
|
3326
|
-
writeLine(` ${colors.dim}Fix: ${cli.fix}${colors.reset}`);
|
|
3327
|
-
}
|
|
3328
|
-
writeLine("");
|
|
3329
|
-
}
|
|
3330
|
-
function printCapabilities(clis) {
|
|
3331
|
-
const installedClis = clis.filter((c) => c.installed);
|
|
3332
|
-
if (installedClis.length === 0) {
|
|
3333
|
-
writeLine(`${formatStatus(false)} No CLIs installed`);
|
|
3334
|
-
return;
|
|
3335
|
-
}
|
|
3336
|
-
const caps = DEFAULT_CAPABILITIES;
|
|
3337
|
-
const bestReasoning = installedClis.reduce(
|
|
3338
|
-
(best, c) => caps[c.name].reasoning > caps[best.name].reasoning ? c : best
|
|
3339
|
-
);
|
|
3340
|
-
const bestContext = installedClis.reduce(
|
|
3341
|
-
(best, c) => caps[c.name].contextWindow > caps[best.name].contextWindow ? c : best
|
|
3342
|
-
);
|
|
3343
|
-
const bestSpeed = installedClis.reduce(
|
|
3344
|
-
(best, c) => caps[c.name].speed > caps[best.name].speed ? c : best
|
|
3345
|
-
);
|
|
3346
|
-
const contextTokensK = (caps[bestContext.name].contextWindow / 1e3).toFixed(0);
|
|
3347
|
-
writeLine(
|
|
3348
|
-
`${formatStatus(true)} Complex reasoning: ${colors.bold}${capitalize(bestReasoning.name)}${colors.reset}`
|
|
3349
|
-
);
|
|
3350
|
-
writeLine(
|
|
3351
|
-
`${formatStatus(true)} Large context: ${colors.bold}${capitalize(bestContext.name)}${colors.reset} (${contextTokensK}K tokens)`
|
|
3352
|
-
);
|
|
3353
|
-
writeLine(
|
|
3354
|
-
`${formatStatus(true)} Fast execution: ${colors.bold}${capitalize(bestSpeed.name)}${colors.reset}`
|
|
3355
|
-
);
|
|
3356
|
-
}
|
|
3357
|
-
function printNodeVersionCheck(check) {
|
|
3358
|
-
const versionText = check.supported ? `${colors.green}${check.version}${colors.reset}` : `${colors.yellow}${check.version}${colors.reset}`;
|
|
3359
|
-
writeLine(`${formatStatus(check.supported, !check.supported)} Node.js version: ${versionText}`);
|
|
3360
|
-
if (!check.supported) {
|
|
3361
|
-
writeLine(
|
|
3362
|
-
` ${colors.dim}Warning: Node.js ${String(REQUIRED_NODE_MAJOR)}.x LTS required${colors.reset}`
|
|
3363
|
-
);
|
|
3364
|
-
}
|
|
3365
|
-
}
|
|
3366
|
-
function printApiKeysCheck(keys) {
|
|
3367
|
-
const configuredCount = keys.filter((k) => k.configured).length;
|
|
3368
|
-
const configuredNames = keys.filter((k) => k.configured).map((k) => k.name);
|
|
3369
|
-
const hasAny = configuredCount > 0;
|
|
3370
|
-
writeLine(
|
|
3371
|
-
`${formatStatus(hasAny, !hasAny)} API keys configured: ${String(configuredCount)} of ${String(keys.length)}`
|
|
3372
|
-
);
|
|
3373
|
-
if (hasAny) {
|
|
3374
|
-
writeLine(` ${colors.dim}Keys: ${configuredNames.join(", ")}${colors.reset}`);
|
|
3375
|
-
} else {
|
|
3376
|
-
writeLine(
|
|
3377
|
-
` ${colors.dim}Set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_AI_API_KEY${colors.reset}`
|
|
3378
|
-
);
|
|
3379
|
-
}
|
|
3380
|
-
}
|
|
3381
|
-
function printConfigFileCheck(check) {
|
|
3382
|
-
if (check.found && check.path !== null) {
|
|
3383
|
-
writeLine(`${formatStatus(true)} Configuration loaded: ${check.path}`);
|
|
3384
|
-
} else {
|
|
3385
|
-
writeLine(`${formatStatus(false, true)} Configuration file: Not found`);
|
|
3386
|
-
writeLine(` ${colors.dim}Run: nexus-agents config init${colors.reset}`);
|
|
3387
|
-
}
|
|
3388
|
-
}
|
|
3389
|
-
function printRegistryAdvisory(advisory) {
|
|
3390
|
-
const allAvailable = advisory.unavailableModels === 0;
|
|
3391
|
-
const countText = `${String(advisory.availableModels)} of ${String(advisory.totalModels)}`;
|
|
3392
|
-
writeLine(`${formatStatus(allAvailable, !allAvailable)} Models available: ${countText}`);
|
|
3393
|
-
if (advisory.unavailableModels > 0) {
|
|
3394
|
-
const missing = advisory.models.filter((m) => !m.available);
|
|
3395
|
-
for (const m of missing) {
|
|
3396
|
-
writeLine(` ${colors.dim}${m.displayName} \u2014 ${m.reason}${colors.reset}`);
|
|
3397
|
-
}
|
|
3398
|
-
}
|
|
3399
|
-
const ageText = `${String(advisory.registryAgeDays)} days old`;
|
|
3400
|
-
if (advisory.registryStale) {
|
|
3401
|
-
writeLine(
|
|
3402
|
-
`${colors.yellow}${symbols.warn}${colors.reset} Model registry is ${ageText} \u2014 may have stale model data`
|
|
3403
|
-
);
|
|
3404
|
-
} else {
|
|
3405
|
-
writeLine(`${formatStatus(true)} Model registry: ${ageText}`);
|
|
3406
|
-
}
|
|
3407
|
-
}
|
|
3408
|
-
function printLearningPersistence(check) {
|
|
3409
|
-
if (!check.enabled) {
|
|
3410
|
-
writeLine(`${formatStatus(true)} Learning persistence: ${colors.dim}Disabled${colors.reset}`);
|
|
3411
|
-
writeLine(` ${colors.dim}Set NEXUS_PERSIST_LEARNING=true to enable${colors.reset}`);
|
|
3412
|
-
return;
|
|
3413
|
-
}
|
|
3414
|
-
const healthy = check.dirExists && check.dirWritable && check.error === null;
|
|
3415
|
-
writeLine(`${formatStatus(healthy, !healthy)} Learning persistence: Enabled`);
|
|
3416
|
-
if (check.error !== null) {
|
|
3417
|
-
writeLine(` ${colors.red}Error: ${check.error}${colors.reset}`);
|
|
3418
|
-
return;
|
|
3419
|
-
}
|
|
3420
|
-
const dirStatus = check.dirExists ? check.dirWritable ? `${colors.green}writable${colors.reset}` : `${colors.red}not writable${colors.reset}` : `${colors.yellow}not created yet${colors.reset}`;
|
|
3421
|
-
writeLine(` Data directory: ${dirStatus}`);
|
|
3422
|
-
writeLine(` Outcomes: ${String(check.outcomeCount)} recorded`);
|
|
3423
|
-
writeLine(` Distilled rules: ${String(check.ruleCount)} active`);
|
|
3424
|
-
if (check.rulesLastSaved !== null) {
|
|
3425
|
-
writeLine(` Rules last saved: ${check.rulesLastSaved}`);
|
|
3426
|
-
}
|
|
3427
|
-
}
|
|
3428
|
-
function printSqliteCheck(check) {
|
|
3429
|
-
if (check.available) {
|
|
3430
|
-
writeLine(
|
|
3431
|
-
`${formatStatus(true)} SQLite (better-sqlite3): ${colors.green}Available${colors.reset}`
|
|
3432
|
-
);
|
|
3433
|
-
} else {
|
|
3434
|
-
writeLine(
|
|
3435
|
-
`${formatStatus(false, true)} SQLite (better-sqlite3): ${colors.yellow}Not available${colors.reset}`
|
|
3436
|
-
);
|
|
3437
|
-
writeLine(
|
|
3438
|
-
` ${colors.dim}Memory backends (agentic, adaptive, typed) require it${colors.reset}`
|
|
3439
|
-
);
|
|
3440
|
-
writeLine(` ${colors.dim}Fix: npm install -g better-sqlite3${colors.reset}`);
|
|
3441
|
-
}
|
|
3442
|
-
}
|
|
3443
|
-
function printDataDirectory(check) {
|
|
3444
|
-
if (check.rootExists) {
|
|
3445
|
-
const existCount = check.subdirectories.filter((d) => d.exists).length;
|
|
3446
|
-
const totalCount = check.subdirectories.length;
|
|
3447
|
-
const allExist = existCount === totalCount;
|
|
3448
|
-
const allWritable = check.subdirectories.every((d) => !d.exists || d.writable);
|
|
3449
|
-
const healthy = allExist && allWritable;
|
|
3450
|
-
writeLine(
|
|
3451
|
-
`${formatStatus(healthy, !healthy)} Data directory: ${check.rootPath} (${String(existCount)}/${String(totalCount)} subdirs)`
|
|
3452
|
-
);
|
|
3453
|
-
if (!allExist) {
|
|
3454
|
-
const missing = check.subdirectories.filter((d) => !d.exists);
|
|
3455
|
-
for (const dir of missing) {
|
|
3456
|
-
writeLine(` ${colors.dim}Missing: ${dir.name}/${colors.reset}`);
|
|
3457
|
-
}
|
|
3458
|
-
writeLine(` ${colors.dim}Fix: nexus-agents setup${colors.reset}`);
|
|
3459
|
-
}
|
|
3460
|
-
if (!allWritable) {
|
|
3461
|
-
const readonly_ = check.subdirectories.filter((d) => d.exists && !d.writable);
|
|
3462
|
-
for (const dir of readonly_) {
|
|
3463
|
-
writeLine(` ${colors.yellow}Not writable: ${dir.name}/${colors.reset}`);
|
|
3464
|
-
}
|
|
3465
|
-
}
|
|
3466
|
-
} else {
|
|
3467
|
-
writeLine(
|
|
3468
|
-
`${formatStatus(false, true)} Data directory: ${colors.yellow}Not created${colors.reset}`
|
|
3469
|
-
);
|
|
3470
|
-
writeLine(` ${colors.dim}Run: nexus-agents setup${colors.reset}`);
|
|
3471
|
-
}
|
|
3472
|
-
}
|
|
3473
|
-
function printDoctorSummary(result) {
|
|
3474
|
-
const unhealthyCount = result.clis.filter((c) => !c.installed || !c.authenticated).length;
|
|
3475
|
-
const nodeIssue = result.nodeVersion.supported ? 0 : 1;
|
|
3476
|
-
const totalIssues = unhealthyCount + nodeIssue + (result.mcpServerReady ? 0 : 1);
|
|
3477
|
-
const summary = result.allHealthy ? `${colors.green}${colors.bold}Status: Ready${colors.reset}` : `${colors.yellow}${colors.bold}Summary: ${String(totalIssues)} issue(s) found${colors.reset}`;
|
|
3478
|
-
writeLine(summary);
|
|
3479
|
-
writeLine("");
|
|
3480
|
-
}
|
|
3481
|
-
function printDoctorResults(result) {
|
|
3482
|
-
writeLine("");
|
|
3483
|
-
writeLine(`${colors.bold}Nexus Agents Doctor${colors.reset}`);
|
|
3484
|
-
writeLine("===================");
|
|
3485
|
-
writeLine("");
|
|
3486
|
-
writeLine(`${colors.cyan}Checking environment...${colors.reset}`);
|
|
3487
|
-
writeLine("");
|
|
3488
|
-
printNodeVersionCheck(result.nodeVersion);
|
|
3489
|
-
printApiKeysCheck(result.apiKeys);
|
|
3490
|
-
printConfigFileCheck(result.configFile);
|
|
3491
|
-
writeLine("");
|
|
3492
|
-
writeLine(`${colors.cyan}Checking CLI installations...${colors.reset}`);
|
|
3493
|
-
writeLine("");
|
|
3494
|
-
for (const cli of result.clis) {
|
|
3495
|
-
printCliResult(cli);
|
|
3496
|
-
}
|
|
3497
|
-
writeLine(`${colors.cyan}Checking MCP configuration...${colors.reset}`);
|
|
3498
|
-
writeLine("");
|
|
3499
|
-
writeLine(
|
|
3500
|
-
`${formatStatus(result.mcpServerReady)} MCP Server mode: ${result.mcpServerReady ? "Ready" : "Not ready"}`
|
|
3501
|
-
);
|
|
3502
|
-
writeLine(
|
|
3503
|
-
`${formatStatus(result.mcpClientReady)} MCP Client mode: ${result.mcpClientReady ? "Ready (Codex mcp-server)" : "Not ready (Codex not installed)"}`
|
|
3504
|
-
);
|
|
3505
|
-
writeLine("");
|
|
3506
|
-
writeLine(`${colors.cyan}Checking capabilities...${colors.reset}`);
|
|
3507
|
-
writeLine("");
|
|
3508
|
-
printCapabilities(result.clis);
|
|
3509
|
-
writeLine("");
|
|
3510
|
-
writeLine(`${colors.cyan}Checking model registry...${colors.reset}`);
|
|
3511
|
-
writeLine("");
|
|
3512
|
-
printRegistryAdvisory(result.registryAdvisory);
|
|
3513
|
-
writeLine("");
|
|
3514
|
-
writeLine(`${colors.cyan}Checking learning subsystem...${colors.reset}`);
|
|
3515
|
-
writeLine("");
|
|
3516
|
-
printLearningPersistence(result.learningPersistence);
|
|
3517
|
-
writeLine("");
|
|
3518
|
-
writeLine(`${colors.cyan}Checking data storage...${colors.reset}`);
|
|
3519
|
-
writeLine("");
|
|
3520
|
-
printSqliteCheck(result.sqliteCheck);
|
|
3521
|
-
printDataDirectory(result.dataDirectory);
|
|
3522
|
-
writeLine("");
|
|
3523
|
-
printDoctorSummary(result);
|
|
3524
|
-
}
|
|
3525
|
-
|
|
3526
|
-
// src/cli/doctor.ts
|
|
3527
|
-
var REQUIRED_NODE_MAJOR2 = 22;
|
|
3528
|
-
var API_KEY_VARS = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GOOGLE_AI_API_KEY"];
|
|
3529
|
-
var CONFIG_FILE_PATHS = ["./nexus-agents.yaml", "./nexus-agents.yml"];
|
|
3530
|
-
var DATA_SUBDIRECTORIES = [
|
|
3531
|
-
"memory",
|
|
3532
|
-
"memory/beliefs",
|
|
3533
|
-
"learning",
|
|
3534
|
-
"sessions",
|
|
3535
|
-
"audit",
|
|
3536
|
-
"voting",
|
|
3537
|
-
"auth",
|
|
3538
|
-
"research",
|
|
3539
|
-
"checkpoints"
|
|
3540
|
-
];
|
|
3541
|
-
function getFixCommand(name, issue) {
|
|
3542
|
-
const commands = {
|
|
3543
|
-
claude: {
|
|
3544
|
-
install: "npm install -g @anthropic-ai/claude-code",
|
|
3545
|
-
upgrade: "npm update -g @anthropic-ai/claude-code",
|
|
3546
|
-
auth: "claude auth login"
|
|
3547
|
-
},
|
|
3548
|
-
gemini: {
|
|
3549
|
-
install: "npm install -g @google/gemini-cli",
|
|
3550
|
-
upgrade: "npm update -g @google/gemini-cli",
|
|
3551
|
-
auth: "gemini auth login"
|
|
3552
|
-
},
|
|
3553
|
-
codex: {
|
|
3554
|
-
install: "npm install -g @openai/codex",
|
|
3555
|
-
upgrade: "npm update -g @openai/codex",
|
|
3556
|
-
auth: "codex auth login"
|
|
3557
|
-
},
|
|
3558
|
-
opencode: {
|
|
3559
|
-
install: "npm install -g opencode-ai",
|
|
3560
|
-
upgrade: "npm update -g opencode-ai",
|
|
3561
|
-
auth: "opencode auth login"
|
|
3562
|
-
}
|
|
3563
|
-
};
|
|
3564
|
-
return commands[name][issue] ?? "";
|
|
3565
|
-
}
|
|
3566
|
-
function createNotFoundResult(name, errorMsg) {
|
|
3567
|
-
return {
|
|
3568
|
-
name,
|
|
3569
|
-
installed: false,
|
|
3570
|
-
version: "N/A",
|
|
3571
|
-
versionStatus: "unsupported",
|
|
3572
|
-
authenticated: false,
|
|
3573
|
-
error: errorMsg,
|
|
3574
|
-
fix: getFixCommand(name, "install")
|
|
3575
|
-
};
|
|
3576
|
-
}
|
|
3577
|
-
function detectAuthMethod(name) {
|
|
3578
|
-
const authMethods = {
|
|
3579
|
-
claude: "CLI auth",
|
|
3580
|
-
gemini: "ADC/CLI auth",
|
|
3581
|
-
codex: "CLI auth",
|
|
3582
|
-
opencode: "CLI auth"
|
|
3583
|
-
};
|
|
3584
|
-
return authMethods[name];
|
|
3585
|
-
}
|
|
3586
|
-
function createHealthyResult(name, health, capacity) {
|
|
3587
|
-
const authenticated = health.healthy;
|
|
3588
|
-
const result = {
|
|
3589
|
-
name,
|
|
3590
|
-
installed: true,
|
|
3591
|
-
version: health.version,
|
|
3592
|
-
versionStatus: health.versionStatus,
|
|
3593
|
-
authenticated,
|
|
3594
|
-
...authenticated && { authMethod: detectAuthMethod(name) },
|
|
3595
|
-
...capacity !== void 0 && { capacity }
|
|
3596
|
-
};
|
|
3597
|
-
if (health.message !== void 0 && health.message !== "") {
|
|
3598
|
-
return { ...result, error: health.message };
|
|
3599
|
-
}
|
|
3600
|
-
if (!authenticated) {
|
|
3601
|
-
return { ...result, fix: getFixCommand(name, "auth") };
|
|
3602
|
-
}
|
|
3603
|
-
if (health.versionStatus === "outdated") {
|
|
3604
|
-
return { ...result, fix: getFixCommand(name, "upgrade") };
|
|
3605
|
-
}
|
|
3606
|
-
return result;
|
|
3607
|
-
}
|
|
3608
|
-
async function checkCli(name) {
|
|
3609
|
-
const adapters = createAllAdapters();
|
|
3610
|
-
const adapter = adapters.get(name);
|
|
3611
|
-
if (!adapter) {
|
|
3612
|
-
return createNotFoundResult(name, "Adapter not available");
|
|
3613
|
-
}
|
|
3614
|
-
try {
|
|
3615
|
-
const health = await adapter.healthCheck();
|
|
3616
|
-
let capacity;
|
|
3617
|
-
try {
|
|
3618
|
-
capacity = await adapter.getCapacity();
|
|
3619
|
-
} catch (capErr) {
|
|
3620
|
-
void capErr;
|
|
3621
|
-
}
|
|
3622
|
-
return createHealthyResult(name, health, capacity);
|
|
3623
|
-
} catch (error) {
|
|
3624
|
-
const message = getErrorMessage(error);
|
|
3625
|
-
const isNotFound = message.includes("ENOENT") || message.includes("not found");
|
|
3626
|
-
return createNotFoundResult(name, isNotFound ? "Not found in PATH" : message);
|
|
3627
|
-
}
|
|
3628
|
-
}
|
|
3629
|
-
function checkNodeVersion() {
|
|
3630
|
-
const version = process.version;
|
|
3631
|
-
const major = Number(version.slice(1).split(".")[0]);
|
|
3632
|
-
return {
|
|
3633
|
-
version,
|
|
3634
|
-
major,
|
|
3635
|
-
supported: major >= REQUIRED_NODE_MAJOR2
|
|
3636
|
-
};
|
|
3637
|
-
}
|
|
3638
|
-
function checkApiKeys() {
|
|
3639
|
-
return API_KEY_VARS.map((name) => ({
|
|
3640
|
-
name,
|
|
3641
|
-
configured: typeof process.env[name] === "string" && process.env[name] !== ""
|
|
3642
|
-
}));
|
|
3643
|
-
}
|
|
3644
|
-
function checkConfigFile() {
|
|
3645
|
-
for (const configPath of CONFIG_FILE_PATHS) {
|
|
3646
|
-
if (existsSync(configPath)) {
|
|
3647
|
-
return { found: true, path: configPath };
|
|
3648
|
-
}
|
|
3649
|
-
}
|
|
3650
|
-
return { found: false, path: null };
|
|
3651
|
-
}
|
|
3652
|
-
function checkMcpServerReady() {
|
|
3653
|
-
try {
|
|
3654
|
-
const result = createServer({ name: "nexus-agents-doctor-check" });
|
|
3655
|
-
return result.ok;
|
|
3656
|
-
} catch {
|
|
3657
|
-
return false;
|
|
3658
|
-
}
|
|
3659
|
-
}
|
|
3660
|
-
function buildRegistryAdvisory(cliResults) {
|
|
3661
|
-
const installedClis = new Set(cliResults.filter((c) => c.installed).map((c) => c.name));
|
|
3662
|
-
const models = DEFAULT_MODEL_CAPABILITIES.models.filter((m) => m.cliName !== void 0).map((m) => {
|
|
3663
|
-
const cliName = m.cliName ?? "";
|
|
3664
|
-
const available = cliName.length > 0 && installedClis.has(cliName);
|
|
3665
|
-
const reason = available ? `${cliName} CLI is installed` : `${cliName} CLI is not installed`;
|
|
3666
|
-
return { modelId: m.id, displayName: m.displayName, cliName, available, reason };
|
|
3667
|
-
});
|
|
3668
|
-
const STALE_THRESHOLD_DAYS = 30;
|
|
3669
|
-
const updatedAt = new Date(DEFAULT_MODEL_CAPABILITIES.updatedAt);
|
|
3670
|
-
const nowMs = getTimeProvider().now();
|
|
3671
|
-
const ageDays = Math.floor((nowMs - updatedAt.getTime()) / (1e3 * 60 * 60 * 24));
|
|
3672
|
-
return {
|
|
3673
|
-
totalModels: models.length,
|
|
3674
|
-
availableModels: models.filter((m) => m.available).length,
|
|
3675
|
-
unavailableModels: models.filter((m) => !m.available).length,
|
|
3676
|
-
models,
|
|
3677
|
-
registryAgeDays: ageDays,
|
|
3678
|
-
registryStale: ageDays > STALE_THRESHOLD_DAYS
|
|
3679
|
-
};
|
|
3680
|
-
}
|
|
3681
|
-
function countJsonlLines(filePath) {
|
|
3682
|
-
if (!existsSync(filePath)) return 0;
|
|
3683
|
-
return readFileSync(filePath, "utf-8").split("\n").filter((l) => l.trim().length > 0).length;
|
|
3684
|
-
}
|
|
3685
|
-
function readRulesMetadata(filePath) {
|
|
3686
|
-
if (!existsSync(filePath)) return { count: 0, savedAt: null };
|
|
3687
|
-
try {
|
|
3688
|
-
const raw = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
3689
|
-
const rules = raw["rules"];
|
|
3690
|
-
const saved = raw["savedAt"];
|
|
3691
|
-
return {
|
|
3692
|
-
count: Array.isArray(rules) ? rules.length : 0,
|
|
3693
|
-
savedAt: typeof saved === "string" ? saved : null
|
|
3694
|
-
};
|
|
3695
|
-
} catch {
|
|
3696
|
-
return { count: 0, savedAt: null };
|
|
3697
|
-
}
|
|
3698
|
-
}
|
|
3699
|
-
function checkDirAccess(dir) {
|
|
3700
|
-
const exists = existsSync(dir);
|
|
3701
|
-
if (!exists) return { exists: false, writable: false };
|
|
3702
|
-
try {
|
|
3703
|
-
accessSync(dir, fsConstants.W_OK);
|
|
3704
|
-
return { exists: true, writable: true };
|
|
3705
|
-
} catch {
|
|
3706
|
-
return { exists: true, writable: false };
|
|
3707
|
-
}
|
|
3708
|
-
}
|
|
3709
|
-
var DISABLED_CHECK = {
|
|
3710
|
-
enabled: false,
|
|
3711
|
-
dirExists: false,
|
|
3712
|
-
dirWritable: false,
|
|
3713
|
-
outcomeCount: 0,
|
|
3714
|
-
ruleCount: 0,
|
|
3715
|
-
rulesLastSaved: null,
|
|
3716
|
-
error: null
|
|
3717
|
-
};
|
|
3718
|
-
function checkLearningPersistence() {
|
|
3719
|
-
if (!isPersistenceEnabled()) return DISABLED_CHECK;
|
|
3720
|
-
try {
|
|
3721
|
-
const { exists: dirExists, writable: dirWritable } = checkDirAccess(LEARNING_DIR);
|
|
3722
|
-
const outcomeCount = countJsonlLines(OUTCOMES_FILE);
|
|
3723
|
-
const { count: ruleCount, savedAt: rulesLastSaved } = readRulesMetadata(RULES_FILE);
|
|
3724
|
-
return {
|
|
3725
|
-
enabled: true,
|
|
3726
|
-
dirExists,
|
|
3727
|
-
dirWritable,
|
|
3728
|
-
outcomeCount,
|
|
3729
|
-
ruleCount,
|
|
3730
|
-
rulesLastSaved,
|
|
3731
|
-
error: null
|
|
3732
|
-
};
|
|
3733
|
-
} catch (error) {
|
|
3734
|
-
return {
|
|
3735
|
-
enabled: true,
|
|
3736
|
-
dirExists: false,
|
|
3737
|
-
dirWritable: false,
|
|
3738
|
-
outcomeCount: 0,
|
|
3739
|
-
ruleCount: 0,
|
|
3740
|
-
rulesLastSaved: null,
|
|
3741
|
-
error: getErrorMessage(error)
|
|
3742
|
-
};
|
|
3743
|
-
}
|
|
3744
|
-
}
|
|
3745
|
-
async function checkSqlite() {
|
|
3746
|
-
try {
|
|
3747
|
-
await import("better-sqlite3");
|
|
3748
|
-
return { available: true, error: null };
|
|
3749
|
-
} catch (error) {
|
|
3750
|
-
const msg = getErrorMessage(error);
|
|
3751
|
-
const isNotFound = msg.includes("Cannot find") || msg.includes("MODULE_NOT_FOUND");
|
|
3752
|
-
return {
|
|
3753
|
-
available: false,
|
|
3754
|
-
error: isNotFound ? "better-sqlite3 not installed \u2014 5 memory backends unavailable" : `better-sqlite3 load error: ${msg}`
|
|
3755
|
-
};
|
|
3756
|
-
}
|
|
3757
|
-
}
|
|
3758
|
-
function checkDataDirectory() {
|
|
3759
|
-
const rootPath = join(homedir(), ".nexus-agents");
|
|
3760
|
-
const rootExists = existsSync(rootPath);
|
|
3761
|
-
const subdirectories = DATA_SUBDIRECTORIES.map((name) => {
|
|
3762
|
-
const fullPath = join(rootPath, name);
|
|
3763
|
-
const exists = existsSync(fullPath);
|
|
3764
|
-
return { name, path: fullPath, exists, writable: exists && isWritable(fullPath) };
|
|
3765
|
-
});
|
|
3766
|
-
return { rootExists, rootPath, subdirectories };
|
|
3767
|
-
}
|
|
3768
|
-
function isWritable(dirPath) {
|
|
3769
|
-
try {
|
|
3770
|
-
accessSync(dirPath, fsConstants.W_OK);
|
|
3771
|
-
return true;
|
|
3772
|
-
} catch {
|
|
3773
|
-
return false;
|
|
3774
|
-
}
|
|
3775
|
-
}
|
|
3776
|
-
async function runDoctor() {
|
|
3777
|
-
const clis = await Promise.all([
|
|
3778
|
-
checkCli("claude"),
|
|
3779
|
-
checkCli("gemini"),
|
|
3780
|
-
checkCli("codex"),
|
|
3781
|
-
checkCli("opencode")
|
|
3782
|
-
]);
|
|
3783
|
-
const nodeVersion = checkNodeVersion();
|
|
3784
|
-
const apiKeys = checkApiKeys();
|
|
3785
|
-
const configFile = checkConfigFile();
|
|
3786
|
-
const mcpServerReady = checkMcpServerReady();
|
|
3787
|
-
const codexCheck = clis.find((c) => c.name === "codex");
|
|
3788
|
-
const mcpClientReady = codexCheck?.installed ?? false;
|
|
3789
|
-
const registryAdvisory = buildRegistryAdvisory(clis);
|
|
3790
|
-
const learningPersistence = checkLearningPersistence();
|
|
3791
|
-
const sqliteCheck = await checkSqlite();
|
|
3792
|
-
const dataDirectory = checkDataDirectory();
|
|
3793
|
-
const hasAuthMethod = apiKeys.some((k) => k.configured) || clis.some((c) => c.installed && c.authenticated);
|
|
3794
|
-
const allHealthy = nodeVersion.supported && hasAuthMethod && mcpServerReady && clis.every((c) => c.installed && c.authenticated && c.versionStatus !== "unsupported");
|
|
3795
|
-
return {
|
|
3796
|
-
clis,
|
|
3797
|
-
nodeVersion,
|
|
3798
|
-
apiKeys,
|
|
3799
|
-
configFile,
|
|
3800
|
-
mcpServerReady,
|
|
3801
|
-
mcpClientReady,
|
|
3802
|
-
registryAdvisory,
|
|
3803
|
-
learningPersistence,
|
|
3804
|
-
sqliteCheck,
|
|
3805
|
-
dataDirectory,
|
|
3806
|
-
allHealthy,
|
|
3807
|
-
timestamp: new Date(getTimeProvider().now())
|
|
3808
|
-
};
|
|
3809
|
-
}
|
|
3810
|
-
async function doctorCommand(options = {}) {
|
|
3811
|
-
const result = await runDoctor();
|
|
3812
|
-
printDoctorResults(result);
|
|
3813
|
-
if (options.fix === true) {
|
|
3814
|
-
await runDoctorFix(result);
|
|
3815
|
-
}
|
|
3816
|
-
return result.allHealthy ? 0 : 1;
|
|
3817
|
-
}
|
|
3818
|
-
async function runDoctorFix(result) {
|
|
3819
|
-
const writeLine2 = (text) => {
|
|
3820
|
-
process.stdout.write(text + "\n");
|
|
3821
|
-
};
|
|
3822
|
-
writeLine2("");
|
|
3823
|
-
writeLine2("\x1B[1mAuto-fix\x1B[0m");
|
|
3824
|
-
writeLine2("\u2500".repeat(40));
|
|
3825
|
-
let fixCount = 0;
|
|
3826
|
-
if (!result.dataDirectory.rootExists || result.dataDirectory.subdirectories.some((d) => !d.exists || !d.writable)) {
|
|
3827
|
-
const { runSetup } = await import("./setup-command-ZN7DSP53.js");
|
|
3828
|
-
const setupResult = runSetup({
|
|
3829
|
-
skipMcp: true,
|
|
3830
|
-
skipRules: true,
|
|
3831
|
-
skipHooks: true,
|
|
3832
|
-
skipConfig: true,
|
|
3833
|
-
skipOpencode: true
|
|
3834
|
-
});
|
|
3835
|
-
if (setupResult.success) {
|
|
3836
|
-
writeLine2("\u2713 Created missing data directories");
|
|
3837
|
-
fixCount++;
|
|
3838
|
-
}
|
|
3839
|
-
}
|
|
3840
|
-
if (!result.configFile.found) {
|
|
3841
|
-
const { runConfigInitSync } = await import("./setup-config-ZOJUDXNP.js");
|
|
3842
|
-
const configResult = runConfigInitSync(process.cwd(), false, false);
|
|
3843
|
-
if (configResult.success && configResult.created) {
|
|
3844
|
-
writeLine2(`\u2713 Generated config: ${configResult.path}`);
|
|
3845
|
-
fixCount++;
|
|
3846
|
-
}
|
|
3847
|
-
}
|
|
3848
|
-
if (!result.sqliteCheck.available) {
|
|
3849
|
-
writeLine2("");
|
|
3850
|
-
writeLine2("\u26A0 better-sqlite3 not installed (manual step required):");
|
|
3851
|
-
writeLine2(" npm install -g better-sqlite3");
|
|
3852
|
-
}
|
|
3853
|
-
if (fixCount > 0) {
|
|
3854
|
-
writeLine2("");
|
|
3855
|
-
writeLine2(
|
|
3856
|
-
`\x1B[32m${String(fixCount)} issue(s) fixed.\x1B[0m Re-run \x1B[1mnexus-agents doctor\x1B[0m to verify.`
|
|
3857
|
-
);
|
|
3858
|
-
} else {
|
|
3859
|
-
writeLine2("No auto-fixable issues found.");
|
|
3860
|
-
}
|
|
3861
|
-
writeLine2("");
|
|
3862
|
-
}
|
|
3863
|
-
|
|
3864
|
-
// src/cli/setup-data-dir.ts
|
|
3865
|
-
var NEXUS_DATA_DIR = join2(homedir2(), ".nexus-agents");
|
|
3866
|
-
var RESTRICTED_DIRS = /* @__PURE__ */ new Set(["auth"]);
|
|
3867
|
-
function initDataDirectories(dryRun = false) {
|
|
3868
|
-
const created = [];
|
|
3869
|
-
const alreadyExisted = [];
|
|
3870
|
-
try {
|
|
3871
|
-
ensureDir(NEXUS_DATA_DIR, dryRun, created, alreadyExisted);
|
|
3872
|
-
for (const subdir of DATA_SUBDIRECTORIES) {
|
|
3873
|
-
const mode = RESTRICTED_DIRS.has(subdir) ? 448 : void 0;
|
|
3874
|
-
ensureDir(join2(NEXUS_DATA_DIR, subdir), dryRun, created, alreadyExisted, mode);
|
|
3875
|
-
}
|
|
3876
|
-
return { success: true, rootPath: NEXUS_DATA_DIR, created, alreadyExisted, error: null };
|
|
3877
|
-
} catch (error) {
|
|
3878
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
3879
|
-
return { success: false, rootPath: NEXUS_DATA_DIR, created, alreadyExisted, error: msg };
|
|
3880
|
-
}
|
|
3881
|
-
}
|
|
3882
|
-
function ensureDir(dirPath, dryRun, created, alreadyExisted, mode) {
|
|
3883
|
-
if (existsSync2(dirPath)) {
|
|
3884
|
-
alreadyExisted.push(dirPath);
|
|
3885
|
-
return;
|
|
3886
|
-
}
|
|
3887
|
-
if (!dryRun) {
|
|
3888
|
-
mkdirSync(dirPath, { recursive: true, ...mode !== void 0 ? { mode } : {} });
|
|
3889
|
-
}
|
|
3890
|
-
created.push(dirPath);
|
|
3891
|
-
}
|
|
3892
|
-
|
|
3893
2969
|
export {
|
|
3894
|
-
VERSION,
|
|
3895
2970
|
sleep,
|
|
3896
2971
|
delay,
|
|
3897
2972
|
withTimeout,
|
|
@@ -3900,6 +2975,10 @@ export {
|
|
|
3900
2975
|
getTimeoutForTask,
|
|
3901
2976
|
estimateTaskComplexity2 as estimateTaskComplexity,
|
|
3902
2977
|
getTimeoutForTaskAuto,
|
|
2978
|
+
calculateBackoffDelay,
|
|
2979
|
+
isRetryableError,
|
|
2980
|
+
categorizeError,
|
|
2981
|
+
executeCliRetryLoop,
|
|
3903
2982
|
BaseCliAdapter,
|
|
3904
2983
|
SubprocessCliAdapter,
|
|
3905
2984
|
asRecord,
|
|
@@ -3922,23 +3001,6 @@ export {
|
|
|
3922
3001
|
createCliAdapter,
|
|
3923
3002
|
createAllAdapters,
|
|
3924
3003
|
isCliAvailable,
|
|
3925
|
-
getAvailableClis
|
|
3926
|
-
STOPWORDS,
|
|
3927
|
-
tokenize,
|
|
3928
|
-
tokenizeFiltered,
|
|
3929
|
-
stringifyValue,
|
|
3930
|
-
capitalize,
|
|
3931
|
-
capitalizeWords,
|
|
3932
|
-
capitalizeKebab,
|
|
3933
|
-
truncateWithInfo,
|
|
3934
|
-
truncateSentence,
|
|
3935
|
-
DEFAULT_TASK_TTL_MS,
|
|
3936
|
-
clampTaskTtl,
|
|
3937
|
-
doctorCommand,
|
|
3938
|
-
initDataDirectories,
|
|
3939
|
-
createServer,
|
|
3940
|
-
connectTransport,
|
|
3941
|
-
startStdioServer,
|
|
3942
|
-
closeServer
|
|
3004
|
+
getAvailableClis
|
|
3943
3005
|
};
|
|
3944
|
-
//# sourceMappingURL=chunk-
|
|
3006
|
+
//# sourceMappingURL=chunk-ZBZJHXRT.js.map
|