nexus-agents 2.28.0 → 2.29.1
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 +11 -10
- package/dist/adaptive-memory-RST6DZYR.js +15 -0
- package/dist/chunk-2UR7YN6T.js +700 -0
- package/dist/chunk-2UR7YN6T.js.map +1 -0
- package/dist/chunk-2UUUKVNR.js +61 -0
- package/dist/chunk-2UUUKVNR.js.map +1 -0
- package/dist/chunk-3EVVQ32X.js +838 -0
- package/dist/chunk-3EVVQ32X.js.map +1 -0
- package/dist/{chunk-UGNLR4NZ.js → chunk-3GXDN4AX.js} +2 -2
- package/dist/{chunk-LKSTILEE.js → chunk-4AGPR6XZ.js} +194 -1461
- package/dist/chunk-4AGPR6XZ.js.map +1 -0
- package/dist/chunk-4HA5PAL7.js +246 -0
- package/dist/chunk-4HA5PAL7.js.map +1 -0
- package/dist/chunk-633WH2ML.js +127 -0
- package/dist/chunk-633WH2ML.js.map +1 -0
- package/dist/chunk-7SKAKA4I.js +90 -0
- package/dist/chunk-7SKAKA4I.js.map +1 -0
- package/dist/chunk-AP2FD37C.js +127 -0
- package/dist/chunk-AP2FD37C.js.map +1 -0
- package/dist/chunk-BC3M4VLP.js +359 -0
- package/dist/chunk-BC3M4VLP.js.map +1 -0
- package/dist/chunk-BQ4YXGGQ.js +127 -0
- package/dist/chunk-BQ4YXGGQ.js.map +1 -0
- package/dist/chunk-C2C5ONFR.js +195 -0
- package/dist/chunk-C2C5ONFR.js.map +1 -0
- package/dist/chunk-CGWRJ4EM.js +12661 -0
- package/dist/chunk-CGWRJ4EM.js.map +1 -0
- package/dist/chunk-CLYZ7FWP.js +30 -0
- package/dist/chunk-CLYZ7FWP.js.map +1 -0
- package/dist/chunk-ED6VQWNG.js +63 -0
- package/dist/chunk-ED6VQWNG.js.map +1 -0
- package/dist/{chunk-L2SHSW4T.js → chunk-ELIFTCYM.js} +2705 -2473
- package/dist/chunk-ELIFTCYM.js.map +1 -0
- package/dist/chunk-FYJVXQHX.js +944 -0
- package/dist/chunk-FYJVXQHX.js.map +1 -0
- package/dist/{chunk-QZEAD6AG.js → chunk-FZFZ77UJ.js} +19434 -38182
- package/dist/chunk-FZFZ77UJ.js.map +1 -0
- package/dist/chunk-IECE6DBS.js +1585 -0
- package/dist/chunk-IECE6DBS.js.map +1 -0
- package/dist/chunk-LLGUX44Z.js +356 -0
- package/dist/chunk-LLGUX44Z.js.map +1 -0
- package/dist/chunk-MRU6L7YJ.js +931 -0
- package/dist/chunk-MRU6L7YJ.js.map +1 -0
- package/dist/chunk-N5SY7V45.js +44 -0
- package/dist/chunk-N5SY7V45.js.map +1 -0
- package/dist/chunk-OOIPRRPX.js +3790 -0
- package/dist/chunk-OOIPRRPX.js.map +1 -0
- package/dist/chunk-SRECH7OQ.js +354 -0
- package/dist/chunk-SRECH7OQ.js.map +1 -0
- package/dist/{chunk-YSDUVCCZ.js → chunk-VKRMXD62.js} +6 -6
- package/dist/{chunk-E7EX2KQJ.js → chunk-XU3NADFE.js} +2 -2
- package/dist/chunk-YSTJEMQX.js +122 -0
- package/dist/chunk-YSTJEMQX.js.map +1 -0
- package/dist/cli-circuit-breaker-5FAODXVY.js +13 -0
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +1485 -683
- package/dist/cli.js.map +1 -1
- package/dist/codebase-search-CZUA37RU.js +9 -0
- package/dist/composite-router-FNW7ZWL7.js +17 -0
- package/dist/composite-router-FNW7ZWL7.js.map +1 -0
- package/dist/consensus-vote-757YULIP.js +25 -0
- package/dist/consensus-vote-757YULIP.js.map +1 -0
- package/dist/{dist-H5XNXVAV.js → dist-CV74KUT7.js} +1521 -972
- package/dist/dist-CV74KUT7.js.map +1 -0
- package/dist/{doctor-deep-BDE2PHVX.js → doctor-deep-LMCEAFU4.js} +4 -3
- package/dist/doctor-deep-LMCEAFU4.js.map +1 -0
- package/dist/expert-bridge-L2D4OXOR.js +10 -0
- package/dist/expert-bridge-L2D4OXOR.js.map +1 -0
- package/dist/expert-config-A5CHKUGI.js +22 -0
- package/dist/expert-config-A5CHKUGI.js.map +1 -0
- package/dist/factory-ELEDP2WD.js +17 -0
- package/dist/factory-ELEDP2WD.js.map +1 -0
- package/dist/factory-IDTIBX6B.js +14 -0
- package/dist/factory-IDTIBX6B.js.map +1 -0
- package/dist/index.d.ts +1814 -777
- package/dist/index.js +785 -355
- package/dist/index.js.map +1 -1
- package/dist/issue-triage-SJPKJLXH.js +15 -0
- package/dist/issue-triage-SJPKJLXH.js.map +1 -0
- package/dist/learning-persistence-WMWZJZ35.js +16 -0
- package/dist/learning-persistence-WMWZJZ35.js.map +1 -0
- package/dist/mcp-config-2OXIOMJ6.js +12 -0
- package/dist/mcp-config-2OXIOMJ6.js.map +1 -0
- package/dist/mobimem-5S3VLNSU.js +13 -0
- package/dist/mobimem-5S3VLNSU.js.map +1 -0
- package/dist/{model-capabilities-types-CSWO27YN.d.ts → model-capabilities-types-B57GZryc.d.ts} +1 -1
- package/dist/repo-analyze-HWMXSK5C.js +24 -0
- package/dist/repo-analyze-HWMXSK5C.js.map +1 -0
- package/dist/repo-security-plan-MUFDGWSQ.js +17 -0
- package/dist/repo-security-plan-MUFDGWSQ.js.map +1 -0
- package/dist/research-helpers-synthesize-OBQJ5BGX.js +10 -0
- package/dist/research-helpers-synthesize-OBQJ5BGX.js.map +1 -0
- package/dist/routing-memory-3QBQTS4A.js +13 -0
- package/dist/routing-memory-3QBQTS4A.js.map +1 -0
- package/dist/session-memory-VXWLOFRC.js +22 -0
- package/dist/session-memory-VXWLOFRC.js.map +1 -0
- package/dist/{setup-command-SS7LMN7Y.js → setup-command-E6MXO5RZ.js} +9 -5
- package/dist/setup-command-E6MXO5RZ.js.map +1 -0
- package/dist/setup-config-O5F3AZBL.js +10 -0
- package/dist/setup-config-O5F3AZBL.js.map +1 -0
- package/dist/shared-memory-AEO2HJLC.js +8 -0
- package/dist/shared-memory-AEO2HJLC.js.map +1 -0
- package/dist/symbol-extractor-UEBANFSN.js +10 -0
- package/dist/symbol-extractor-UEBANFSN.js.map +1 -0
- package/dist/weather-report-MUGSIOU5.js +15 -0
- package/dist/weather-report-MUGSIOU5.js.map +1 -0
- package/package.json +18 -19
- package/dist/chunk-L2SHSW4T.js.map +0 -1
- package/dist/chunk-LKSTILEE.js.map +0 -1
- package/dist/chunk-QZEAD6AG.js.map +0 -1
- package/dist/dist-H5XNXVAV.js.map +0 -1
- package/dist/setup-config-DSMOOLVW.js +0 -9
- /package/dist/{doctor-deep-BDE2PHVX.js.map → adaptive-memory-RST6DZYR.js.map} +0 -0
- /package/dist/{chunk-UGNLR4NZ.js.map → chunk-3GXDN4AX.js.map} +0 -0
- /package/dist/{chunk-YSDUVCCZ.js.map → chunk-VKRMXD62.js.map} +0 -0
- /package/dist/{chunk-E7EX2KQJ.js.map → chunk-XU3NADFE.js.map} +0 -0
- /package/dist/{setup-command-SS7LMN7Y.js.map → cli-circuit-breaker-5FAODXVY.js.map} +0 -0
- /package/dist/{setup-config-DSMOOLVW.js.map → codebase-search-CZUA37RU.js.map} +0 -0
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
CliCircuitBreaker,
|
|
3
|
+
DEFAULT_CIRCUIT_BREAKER_CONFIG
|
|
4
|
+
} from "./chunk-LLGUX44Z.js";
|
|
5
|
+
import {
|
|
3
6
|
CLI_SUBPROCESS_TIMEOUTS,
|
|
4
7
|
CLI_TIMEOUTS,
|
|
5
8
|
CLI_VERSION_REQUIREMENTS,
|
|
@@ -7,14 +10,8 @@ import {
|
|
|
7
10
|
DEFAULT_CAPABILITIES,
|
|
8
11
|
DEFAULT_MODEL_CAPABILITIES,
|
|
9
12
|
DEFAULT_MODEL_PER_CLI,
|
|
10
|
-
ErrorCode,
|
|
11
|
-
LEARNING_DIR,
|
|
12
|
-
NexusError,
|
|
13
|
-
OUTCOMES_FILE,
|
|
14
|
-
RULES_FILE,
|
|
15
13
|
buildModelInfo,
|
|
16
14
|
clampPercent,
|
|
17
|
-
colors,
|
|
18
15
|
createLogger,
|
|
19
16
|
detectTaskCategory,
|
|
20
17
|
err,
|
|
@@ -25,36 +22,17 @@ import {
|
|
|
25
22
|
getOutcomeStore,
|
|
26
23
|
getRandomProvider,
|
|
27
24
|
getTimeProvider,
|
|
28
|
-
|
|
29
|
-
ok
|
|
30
|
-
|
|
31
|
-
writeLine
|
|
32
|
-
} from "./chunk-L2SHSW4T.js";
|
|
25
|
+
isRateLimitText,
|
|
26
|
+
ok
|
|
27
|
+
} from "./chunk-ELIFTCYM.js";
|
|
33
28
|
|
|
34
|
-
// src/
|
|
35
|
-
|
|
29
|
+
// src/cli-adapters/subprocess-adapter.ts
|
|
30
|
+
import { spawn } from "child_process";
|
|
36
31
|
|
|
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
|
-
}
|
|
32
|
+
// src/cli-adapters/base-adapter.ts
|
|
33
|
+
import { exec } from "child_process";
|
|
34
|
+
import { promisify } from "util";
|
|
35
|
+
import semver from "semver";
|
|
58
36
|
|
|
59
37
|
// src/cli-adapters/cli-timeout-helpers.ts
|
|
60
38
|
function estimateTaskComplexity(taskDescription) {
|
|
@@ -75,7 +53,19 @@ function estimateTaskComplexity(taskDescription) {
|
|
|
75
53
|
if (complexIndicators.some((indicator) => lower.includes(indicator))) {
|
|
76
54
|
return "complex";
|
|
77
55
|
}
|
|
78
|
-
const simpleIndicators = [
|
|
56
|
+
const simpleIndicators = [
|
|
57
|
+
"single",
|
|
58
|
+
"quick",
|
|
59
|
+
"one function",
|
|
60
|
+
"simple",
|
|
61
|
+
"small",
|
|
62
|
+
"brief",
|
|
63
|
+
"short",
|
|
64
|
+
"run tests",
|
|
65
|
+
"test suite",
|
|
66
|
+
"exploration",
|
|
67
|
+
"explore"
|
|
68
|
+
];
|
|
79
69
|
if (simpleIndicators.some((indicator) => lower.includes(indicator))) {
|
|
80
70
|
return "simple";
|
|
81
71
|
}
|
|
@@ -123,11 +113,6 @@ function getTimeoutForTaskAuto(cli, taskDescription) {
|
|
|
123
113
|
return getAdaptiveTimeout(cli, taskDescription);
|
|
124
114
|
}
|
|
125
115
|
|
|
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
116
|
// src/cli-adapters/capacity-tracker.ts
|
|
132
117
|
var DEFAULT_TOKEN_LIMITS = {
|
|
133
118
|
claude: 1e5,
|
|
@@ -258,6 +243,98 @@ function createCapacityTracker(cli) {
|
|
|
258
243
|
return new CapacityTracker(getDefaultConfig(cli));
|
|
259
244
|
}
|
|
260
245
|
|
|
246
|
+
// src/utils/async-utils.ts
|
|
247
|
+
function sleep(ms) {
|
|
248
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
249
|
+
}
|
|
250
|
+
var delay = sleep;
|
|
251
|
+
async function withTimeout(promise, timeoutMs, errorMessage) {
|
|
252
|
+
let timeoutId;
|
|
253
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
254
|
+
timeoutId = setTimeout(() => {
|
|
255
|
+
reject(new Error(errorMessage));
|
|
256
|
+
}, timeoutMs);
|
|
257
|
+
});
|
|
258
|
+
try {
|
|
259
|
+
const result = await Promise.race([promise, timeoutPromise]);
|
|
260
|
+
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
261
|
+
return { ok: true, value: result };
|
|
262
|
+
} catch (error) {
|
|
263
|
+
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
264
|
+
return { ok: false, error: getErrorMessage(error) };
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/cli-adapters/cli-retry-loop.ts
|
|
269
|
+
var RETRYABLE_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
270
|
+
"TIMEOUT",
|
|
271
|
+
"RATE_LIMITED",
|
|
272
|
+
"CONNECTION_ERROR"
|
|
273
|
+
]);
|
|
274
|
+
function calculateBackoffDelay(attempt, baseDelayMs, maxDelayMs) {
|
|
275
|
+
const exponentialDelay = baseDelayMs * Math.pow(2, attempt - 1);
|
|
276
|
+
const jitter = getRandomProvider().random() * 0.3 * exponentialDelay;
|
|
277
|
+
const delayMs = exponentialDelay + jitter;
|
|
278
|
+
return Math.min(delayMs, maxDelayMs);
|
|
279
|
+
}
|
|
280
|
+
function isRetryableError(code) {
|
|
281
|
+
return RETRYABLE_ERROR_CODES.has(code);
|
|
282
|
+
}
|
|
283
|
+
function categorizeError(error) {
|
|
284
|
+
switch (error.code) {
|
|
285
|
+
case "TIMEOUT":
|
|
286
|
+
return "timeout";
|
|
287
|
+
case "RATE_LIMITED":
|
|
288
|
+
return "rate_limit";
|
|
289
|
+
case "NOT_AUTHENTICATED":
|
|
290
|
+
return "authentication";
|
|
291
|
+
case "CONNECTION_ERROR":
|
|
292
|
+
return "connection";
|
|
293
|
+
default:
|
|
294
|
+
return "unknown";
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
async function executeCliRetryLoop(executeFn, config) {
|
|
298
|
+
const maxAttempts = config.allowRetry ? config.maxRetries + 1 : 1;
|
|
299
|
+
let lastError;
|
|
300
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
301
|
+
const result = await executeFn();
|
|
302
|
+
if (result.ok) {
|
|
303
|
+
return ok({ response: result.value, retryCount: attempt - 1 });
|
|
304
|
+
}
|
|
305
|
+
lastError = result.error;
|
|
306
|
+
if (config.circuitBreaker !== void 0 && config.circuitBreaker !== null) {
|
|
307
|
+
config.circuitBreaker.recordFailure(categorizeError(lastError));
|
|
308
|
+
}
|
|
309
|
+
if (!shouldRetry(lastError, attempt, maxAttempts, config.circuitBreaker)) {
|
|
310
|
+
return err(lastError);
|
|
311
|
+
}
|
|
312
|
+
const delayMs = calculateBackoffDelay(attempt, config.baseDelayMs, config.maxDelayMs);
|
|
313
|
+
config.logger.debug("Retrying CLI execution", {
|
|
314
|
+
cli: config.cli,
|
|
315
|
+
attempt,
|
|
316
|
+
nextAttempt: attempt + 1,
|
|
317
|
+
delayMs: Math.round(delayMs)
|
|
318
|
+
});
|
|
319
|
+
await delay(delayMs);
|
|
320
|
+
}
|
|
321
|
+
return err(
|
|
322
|
+
lastError ?? {
|
|
323
|
+
code: "UNKNOWN",
|
|
324
|
+
message: "Max retries exceeded",
|
|
325
|
+
cli: config.cli,
|
|
326
|
+
retryable: false
|
|
327
|
+
}
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
function shouldRetry(error, attempt, maxAttempts, circuitBreaker) {
|
|
331
|
+
if (attempt >= maxAttempts) return false;
|
|
332
|
+
if (!error.retryable) return false;
|
|
333
|
+
if (!isRetryableError(error.code)) return false;
|
|
334
|
+
if (circuitBreaker?.getState() === "open") return false;
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
|
|
261
338
|
// src/cli-adapters/base-adapter.ts
|
|
262
339
|
var execAsync = promisify(exec);
|
|
263
340
|
var DEFAULT_CAPACITY_FALLBACK = 1e5;
|
|
@@ -276,8 +353,8 @@ var BaseCliAdapter = class {
|
|
|
276
353
|
initialized = false;
|
|
277
354
|
lastHealthCheck;
|
|
278
355
|
cachedVersion;
|
|
279
|
-
constructor(
|
|
280
|
-
this.logger =
|
|
356
|
+
constructor(logger5) {
|
|
357
|
+
this.logger = logger5 ?? createLogger({ component: "cli-adapter" });
|
|
281
358
|
}
|
|
282
359
|
/**
|
|
283
360
|
* Initializes the capacity tracker.
|
|
@@ -323,47 +400,33 @@ var BaseCliAdapter = class {
|
|
|
323
400
|
return getTimeoutForTaskAuto(this.name, task.content);
|
|
324
401
|
}
|
|
325
402
|
/**
|
|
326
|
-
* Executes task with retry logic.
|
|
403
|
+
* Executes task with retry logic via shared retry loop.
|
|
327
404
|
*/
|
|
328
405
|
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", {
|
|
406
|
+
const result = await executeCliRetryLoop(() => this.executeTask(task, opts), {
|
|
407
|
+
maxRetries: opts.maxRetries,
|
|
408
|
+
allowRetry: opts.allowRetry,
|
|
409
|
+
baseDelayMs: 1e3,
|
|
410
|
+
maxDelayMs: 16e3,
|
|
411
|
+
cli: this.name,
|
|
412
|
+
logger: this.logger
|
|
413
|
+
});
|
|
414
|
+
if (result.ok) {
|
|
415
|
+
this.recordUsage(result.value.response);
|
|
416
|
+
this.logger.info("Task executed successfully", {
|
|
354
417
|
cli: this.name,
|
|
355
|
-
attempt,
|
|
356
|
-
|
|
418
|
+
attempt: result.value.retryCount + 1,
|
|
419
|
+
durationMs: result.value.response.durationMs,
|
|
420
|
+
tokensUsed: result.value.response.usage?.totalTokens
|
|
357
421
|
});
|
|
358
|
-
|
|
422
|
+
return { ok: true, value: result.value.response };
|
|
359
423
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
return !error.retryable || attempt === maxAttempts;
|
|
424
|
+
this.logger.warn("Task execution failed", {
|
|
425
|
+
cli: this.name,
|
|
426
|
+
error: result.error.message,
|
|
427
|
+
retryable: result.error.retryable
|
|
428
|
+
});
|
|
429
|
+
return result;
|
|
367
430
|
}
|
|
368
431
|
/**
|
|
369
432
|
* Performs a health check.
|
|
@@ -518,19 +581,39 @@ var BaseCliAdapter = class {
|
|
|
518
581
|
}
|
|
519
582
|
};
|
|
520
583
|
|
|
521
|
-
// src/
|
|
522
|
-
|
|
523
|
-
var
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
584
|
+
// src/security/output-sanitizer.ts
|
|
585
|
+
var REDACTED_KEY_PLACEHOLDER = "[REDACTED_KEY]";
|
|
586
|
+
var KEY_PATTERNS = [
|
|
587
|
+
// Anthropic: sk-ant-api03-... (at least 20 chars after prefix)
|
|
588
|
+
/sk-ant-[A-Za-z0-9_-]{20,}/g,
|
|
589
|
+
// OpenAI project: sk-proj-... (at least 20 chars after prefix)
|
|
590
|
+
/sk-proj-[A-Za-z0-9_-]{20,}/g,
|
|
591
|
+
// Generic OpenAI: sk-... (at least 20 chars after prefix)
|
|
592
|
+
/sk-[A-Za-z0-9_-]{20,}/g,
|
|
593
|
+
// Google AI / Gemini: AIzaSy... (at least 30 chars total)
|
|
594
|
+
/AIzaSy[A-Za-z0-9_-]{24,}/g,
|
|
595
|
+
// GitHub PAT: ghp_...
|
|
596
|
+
/ghp_[A-Za-z0-9]{20,}/g,
|
|
597
|
+
// GitHub OAuth: gho_...
|
|
598
|
+
/gho_[A-Za-z0-9]{20,}/g,
|
|
599
|
+
// GitLab PAT: glpat-...
|
|
600
|
+
/glpat-[A-Za-z0-9_-]{10,}/g,
|
|
601
|
+
// npm token: npm_...
|
|
602
|
+
/npm_[A-Za-z0-9]{20,}/g,
|
|
603
|
+
// PyPI token: pypi-...
|
|
604
|
+
/pypi-[A-Za-z0-9_-]{20,}/g
|
|
529
605
|
];
|
|
530
|
-
function
|
|
531
|
-
|
|
532
|
-
|
|
606
|
+
function sanitizeOutput(text) {
|
|
607
|
+
if (text === "") return text;
|
|
608
|
+
let result = text;
|
|
609
|
+
for (const pattern of KEY_PATTERNS) {
|
|
610
|
+
pattern.lastIndex = 0;
|
|
611
|
+
result = result.replace(pattern, REDACTED_KEY_PLACEHOLDER);
|
|
612
|
+
}
|
|
613
|
+
return result;
|
|
533
614
|
}
|
|
615
|
+
|
|
616
|
+
// src/cli-adapters/subprocess-adapter.ts
|
|
534
617
|
var PLAINTEXT_FALLBACK_MIN_LENGTH = 30;
|
|
535
618
|
function tryPlaintextFallback(stdout) {
|
|
536
619
|
const trimmed = stdout.trim();
|
|
@@ -572,12 +655,11 @@ var STDERR_CONNECTION_PATTERNS = [
|
|
|
572
655
|
"eaddrinuse",
|
|
573
656
|
"address already in use"
|
|
574
657
|
];
|
|
575
|
-
var STDERR_RATE_LIMIT_PATTERNS = ["rate limit", "quota exceeded", "429", "too many requests"];
|
|
576
658
|
var STDERR_TIMEOUT_PATTERNS = ["timeout", "timed out", "etimedout"];
|
|
577
659
|
function classifyStderrError(stderr) {
|
|
578
660
|
const lower = stderr.toLowerCase();
|
|
579
661
|
if (STDERR_CONNECTION_PATTERNS.some((p) => lower.includes(p))) return "CONNECTION_ERROR";
|
|
580
|
-
if (
|
|
662
|
+
if (isRateLimitText(stderr)) return "RATE_LIMITED";
|
|
581
663
|
if (STDERR_TIMEOUT_PATTERNS.some((p) => lower.includes(p))) return "TIMEOUT";
|
|
582
664
|
return "EXECUTION_ERROR";
|
|
583
665
|
}
|
|
@@ -731,6 +813,8 @@ var SubprocessCliAdapter = class extends BaseCliAdapter {
|
|
|
731
813
|
}
|
|
732
814
|
/** Classify a subprocess close event into a Result. */
|
|
733
815
|
classifyCloseResult(code, state, startTime) {
|
|
816
|
+
state.stdout = sanitizeOutput(state.stdout);
|
|
817
|
+
state.stderr = sanitizeOutput(state.stderr);
|
|
734
818
|
if (code !== 0 && state.stdout === "") {
|
|
735
819
|
if (state.stderr !== "") {
|
|
736
820
|
const msg2 = state.stderr;
|
|
@@ -757,7 +841,7 @@ var SubprocessCliAdapter = class extends BaseCliAdapter {
|
|
|
757
841
|
}
|
|
758
842
|
const text = this.parser.extractResponse(stdout);
|
|
759
843
|
if (text === null) {
|
|
760
|
-
if (
|
|
844
|
+
if (isRateLimitText(stdout)) {
|
|
761
845
|
const snippet2 = stdout.slice(0, 500).trim();
|
|
762
846
|
return err(this.createError("RATE_LIMITED", snippet2));
|
|
763
847
|
}
|
|
@@ -800,7 +884,7 @@ var SubprocessCliAdapter = class extends BaseCliAdapter {
|
|
|
800
884
|
if (error.message.includes("ENOENT")) {
|
|
801
885
|
return err(this.createError("NOT_FOUND", `${this.name} CLI not found`, error));
|
|
802
886
|
}
|
|
803
|
-
return err(this.createError("EXECUTION_ERROR", error.message, error));
|
|
887
|
+
return err(this.createError("EXECUTION_ERROR", sanitizeOutput(error.message), error));
|
|
804
888
|
}
|
|
805
889
|
/**
|
|
806
890
|
* Initializes the adapter and capacity tracker.
|
|
@@ -1305,345 +1389,6 @@ var ResilientGeminiParser = class {
|
|
|
1305
1389
|
}
|
|
1306
1390
|
};
|
|
1307
1391
|
|
|
1308
|
-
// src/cli-adapters/circuit-breaker-types.ts
|
|
1309
|
-
var CircuitErrorCode = {
|
|
1310
|
-
CIRCUIT_OPEN: "CIRCUIT_OPEN",
|
|
1311
|
-
CIRCUIT_HALF_OPEN_REJECTED: "CIRCUIT_HALF_OPEN_REJECTED",
|
|
1312
|
-
EXECUTION_FAILED: "EXECUTION_FAILED"
|
|
1313
|
-
};
|
|
1314
|
-
var CircuitError = class extends NexusError {
|
|
1315
|
-
circuitErrorCode;
|
|
1316
|
-
cliName;
|
|
1317
|
-
circuitState;
|
|
1318
|
-
failureCategory;
|
|
1319
|
-
constructor(message, options) {
|
|
1320
|
-
const baseOptions = {
|
|
1321
|
-
code: ErrorCode.INTERNAL_ERROR,
|
|
1322
|
-
context: {
|
|
1323
|
-
circuitErrorCode: options.circuitErrorCode,
|
|
1324
|
-
cliName: options.cliName,
|
|
1325
|
-
circuitState: options.circuitState
|
|
1326
|
-
}
|
|
1327
|
-
};
|
|
1328
|
-
if (options.cause !== void 0) {
|
|
1329
|
-
baseOptions.cause = options.cause;
|
|
1330
|
-
}
|
|
1331
|
-
if (options.failureCategory !== void 0) {
|
|
1332
|
-
baseOptions.context["failureCategory"] = options.failureCategory;
|
|
1333
|
-
}
|
|
1334
|
-
super(message, baseOptions);
|
|
1335
|
-
this.name = "CircuitError";
|
|
1336
|
-
this.circuitErrorCode = options.circuitErrorCode;
|
|
1337
|
-
this.cliName = options.cliName;
|
|
1338
|
-
this.circuitState = options.circuitState;
|
|
1339
|
-
if (options.failureCategory !== void 0) {
|
|
1340
|
-
this.failureCategory = options.failureCategory;
|
|
1341
|
-
}
|
|
1342
|
-
}
|
|
1343
|
-
};
|
|
1344
|
-
var DEFAULT_CIRCUIT_BREAKER_CONFIG = {
|
|
1345
|
-
failureThreshold: 5,
|
|
1346
|
-
resetTimeoutMs: 3e4,
|
|
1347
|
-
halfOpenSuccessThreshold: 2,
|
|
1348
|
-
countTimeoutsAsFailures: true,
|
|
1349
|
-
countAuthFailuresAsFailures: false,
|
|
1350
|
-
countRateLimitsAsFailures: true,
|
|
1351
|
-
halfOpenMaxRequests: 3
|
|
1352
|
-
};
|
|
1353
|
-
var TIMEOUT_PATTERNS = ["timeout", "timed out"];
|
|
1354
|
-
var AUTH_PATTERNS = ["auth", "unauthorized", "forbidden", "oauth"];
|
|
1355
|
-
var RATE_LIMIT_PATTERNS2 = ["rate limit", "too many requests", "429"];
|
|
1356
|
-
var CONNECTION_PATTERNS = [
|
|
1357
|
-
"connection",
|
|
1358
|
-
"econnrefused",
|
|
1359
|
-
"enotfound",
|
|
1360
|
-
"mcp",
|
|
1361
|
-
"eaddrinuse",
|
|
1362
|
-
"address already in use"
|
|
1363
|
-
];
|
|
1364
|
-
var CRASH_PATTERNS = ["crash", "exited", "killed", "sigterm", "sigkill"];
|
|
1365
|
-
function matchesPatterns(text, patterns) {
|
|
1366
|
-
return patterns.some((pattern) => text.includes(pattern));
|
|
1367
|
-
}
|
|
1368
|
-
function categorizeError(error) {
|
|
1369
|
-
if (!(error instanceof Error)) {
|
|
1370
|
-
return "unknown";
|
|
1371
|
-
}
|
|
1372
|
-
const message = error.message.toLowerCase();
|
|
1373
|
-
const name = error.name.toLowerCase();
|
|
1374
|
-
const combined = `${message} ${name}`;
|
|
1375
|
-
if (matchesPatterns(combined, TIMEOUT_PATTERNS)) return "timeout";
|
|
1376
|
-
if (matchesPatterns(combined, AUTH_PATTERNS)) return "authentication";
|
|
1377
|
-
if (matchesPatterns(combined, RATE_LIMIT_PATTERNS2)) return "rate_limit";
|
|
1378
|
-
if (matchesPatterns(combined, CONNECTION_PATTERNS)) return "connection";
|
|
1379
|
-
if (matchesPatterns(combined, CRASH_PATTERNS)) return "crash";
|
|
1380
|
-
return "unknown";
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
// src/cli-adapters/circuit-breaker.ts
|
|
1384
|
-
var CliCircuitBreaker = class {
|
|
1385
|
-
constructor(cliName, config = DEFAULT_CIRCUIT_BREAKER_CONFIG) {
|
|
1386
|
-
this.cliName = cliName;
|
|
1387
|
-
this.config = config;
|
|
1388
|
-
this.lastStateChange = getTimeProvider().now();
|
|
1389
|
-
}
|
|
1390
|
-
state = "closed";
|
|
1391
|
-
failureCount = 0;
|
|
1392
|
-
successCount = 0;
|
|
1393
|
-
lastFailureTime = null;
|
|
1394
|
-
lastStateChange;
|
|
1395
|
-
halfOpenRequests = 0;
|
|
1396
|
-
listeners = /* @__PURE__ */ new Set();
|
|
1397
|
-
/**
|
|
1398
|
-
* Executes a function with circuit breaker protection.
|
|
1399
|
-
*/
|
|
1400
|
-
async execute(fn) {
|
|
1401
|
-
const canExecute = this.canExecute();
|
|
1402
|
-
if (!canExecute.ok) {
|
|
1403
|
-
return canExecute;
|
|
1404
|
-
}
|
|
1405
|
-
try {
|
|
1406
|
-
const result = await fn();
|
|
1407
|
-
this.onSuccess();
|
|
1408
|
-
return ok(result);
|
|
1409
|
-
} catch (error) {
|
|
1410
|
-
const category = categorizeError(error);
|
|
1411
|
-
if (this.shouldCountFailure(category)) {
|
|
1412
|
-
this.onFailure(category);
|
|
1413
|
-
}
|
|
1414
|
-
return err(this.createExecutionError(error, category));
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
getState() {
|
|
1418
|
-
this.checkStateTransition();
|
|
1419
|
-
return this.state;
|
|
1420
|
-
}
|
|
1421
|
-
getSnapshot() {
|
|
1422
|
-
this.checkStateTransition();
|
|
1423
|
-
return {
|
|
1424
|
-
state: this.state,
|
|
1425
|
-
failureCount: this.failureCount,
|
|
1426
|
-
successCount: this.successCount,
|
|
1427
|
-
lastFailureTime: this.lastFailureTime,
|
|
1428
|
-
lastStateChange: this.lastStateChange,
|
|
1429
|
-
halfOpenRequests: this.halfOpenRequests,
|
|
1430
|
-
config: this.config
|
|
1431
|
-
};
|
|
1432
|
-
}
|
|
1433
|
-
reset() {
|
|
1434
|
-
const previousState = this.state;
|
|
1435
|
-
this.state = "closed";
|
|
1436
|
-
this.failureCount = 0;
|
|
1437
|
-
this.successCount = 0;
|
|
1438
|
-
this.lastFailureTime = null;
|
|
1439
|
-
this.halfOpenRequests = 0;
|
|
1440
|
-
this.lastStateChange = getTimeProvider().now();
|
|
1441
|
-
if (previousState !== "closed") {
|
|
1442
|
-
this.emitStateChange(previousState, "closed", "Manual reset");
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
recordFailure(category) {
|
|
1446
|
-
if (this.shouldCountFailure(category)) {
|
|
1447
|
-
this.onFailure(category);
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
recordSuccess() {
|
|
1451
|
-
this.onSuccess();
|
|
1452
|
-
}
|
|
1453
|
-
addStateChangeListener(listener) {
|
|
1454
|
-
this.listeners.add(listener);
|
|
1455
|
-
}
|
|
1456
|
-
removeStateChangeListener(listener) {
|
|
1457
|
-
this.listeners.delete(listener);
|
|
1458
|
-
}
|
|
1459
|
-
// -------------------------------------------------------------------------
|
|
1460
|
-
// Private Methods
|
|
1461
|
-
// -------------------------------------------------------------------------
|
|
1462
|
-
canExecute() {
|
|
1463
|
-
this.checkStateTransition();
|
|
1464
|
-
if (this.state === "closed") {
|
|
1465
|
-
return ok(true);
|
|
1466
|
-
}
|
|
1467
|
-
if (this.state === "open") {
|
|
1468
|
-
return err(
|
|
1469
|
-
new CircuitError(`Circuit is open for CLI: ${this.cliName}`, {
|
|
1470
|
-
circuitErrorCode: CircuitErrorCode.CIRCUIT_OPEN,
|
|
1471
|
-
cliName: this.cliName,
|
|
1472
|
-
circuitState: this.state
|
|
1473
|
-
})
|
|
1474
|
-
);
|
|
1475
|
-
}
|
|
1476
|
-
if (this.halfOpenRequests >= this.config.halfOpenMaxRequests) {
|
|
1477
|
-
return err(
|
|
1478
|
-
new CircuitError(`Circuit half-open request limit reached for CLI: ${this.cliName}`, {
|
|
1479
|
-
circuitErrorCode: CircuitErrorCode.CIRCUIT_HALF_OPEN_REJECTED,
|
|
1480
|
-
cliName: this.cliName,
|
|
1481
|
-
circuitState: this.state
|
|
1482
|
-
})
|
|
1483
|
-
);
|
|
1484
|
-
}
|
|
1485
|
-
this.halfOpenRequests++;
|
|
1486
|
-
return ok(true);
|
|
1487
|
-
}
|
|
1488
|
-
checkStateTransition() {
|
|
1489
|
-
if (this.state === "open" && this.lastFailureTime !== null) {
|
|
1490
|
-
const elapsed = getTimeProvider().now() - this.lastFailureTime;
|
|
1491
|
-
if (elapsed >= this.config.resetTimeoutMs) {
|
|
1492
|
-
this.transitionTo("half-open", "Reset timeout elapsed");
|
|
1493
|
-
}
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
|
-
onSuccess() {
|
|
1497
|
-
if (this.state === "closed") {
|
|
1498
|
-
this.failureCount = 0;
|
|
1499
|
-
} else if (this.state === "half-open") {
|
|
1500
|
-
this.successCount++;
|
|
1501
|
-
if (this.successCount >= this.config.halfOpenSuccessThreshold) {
|
|
1502
|
-
const reason = `${String(this.successCount)} consecutive successes in half-open state`;
|
|
1503
|
-
this.transitionTo("closed", reason);
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
onFailure(category) {
|
|
1508
|
-
this.failureCount++;
|
|
1509
|
-
this.lastFailureTime = getTimeProvider().now();
|
|
1510
|
-
if (this.state === "closed" && this.failureCount >= this.config.failureThreshold) {
|
|
1511
|
-
const reason = `Failure threshold (${String(this.config.failureThreshold)}) exceeded`;
|
|
1512
|
-
this.transitionTo("open", reason);
|
|
1513
|
-
} else if (this.state === "half-open") {
|
|
1514
|
-
this.transitionTo("open", `Failure during half-open recovery (category: ${category})`);
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
transitionTo(newState, reason) {
|
|
1518
|
-
const previousState = this.state;
|
|
1519
|
-
this.state = newState;
|
|
1520
|
-
this.lastStateChange = getTimeProvider().now();
|
|
1521
|
-
if (newState === "closed") {
|
|
1522
|
-
this.failureCount = 0;
|
|
1523
|
-
this.successCount = 0;
|
|
1524
|
-
this.halfOpenRequests = 0;
|
|
1525
|
-
} else if (newState === "half-open") {
|
|
1526
|
-
this.successCount = 0;
|
|
1527
|
-
this.halfOpenRequests = 0;
|
|
1528
|
-
}
|
|
1529
|
-
this.emitStateChange(previousState, newState, reason);
|
|
1530
|
-
}
|
|
1531
|
-
emitStateChange(previousState, newState, reason) {
|
|
1532
|
-
const event = {
|
|
1533
|
-
cliName: this.cliName,
|
|
1534
|
-
previousState,
|
|
1535
|
-
newState,
|
|
1536
|
-
timestamp: this.lastStateChange,
|
|
1537
|
-
failureCount: this.failureCount,
|
|
1538
|
-
reason
|
|
1539
|
-
};
|
|
1540
|
-
for (const listener of this.listeners) {
|
|
1541
|
-
try {
|
|
1542
|
-
listener(event);
|
|
1543
|
-
} catch {
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
shouldCountFailure(category) {
|
|
1548
|
-
if (category === "timeout") return this.config.countTimeoutsAsFailures;
|
|
1549
|
-
if (category === "authentication") return this.config.countAuthFailuresAsFailures;
|
|
1550
|
-
if (category === "rate_limit") return this.config.countRateLimitsAsFailures;
|
|
1551
|
-
return true;
|
|
1552
|
-
}
|
|
1553
|
-
createExecutionError(error, category) {
|
|
1554
|
-
const message = getErrorMessage(error);
|
|
1555
|
-
const cause = error instanceof Error ? error : new Error(String(error));
|
|
1556
|
-
return new CircuitError(`CLI execution failed: ${message}`, {
|
|
1557
|
-
circuitErrorCode: CircuitErrorCode.EXECUTION_FAILED,
|
|
1558
|
-
cliName: this.cliName,
|
|
1559
|
-
circuitState: this.state,
|
|
1560
|
-
failureCategory: category,
|
|
1561
|
-
cause
|
|
1562
|
-
});
|
|
1563
|
-
}
|
|
1564
|
-
};
|
|
1565
|
-
var CircuitBreakerRegistry = class {
|
|
1566
|
-
constructor(defaultConfig = {}) {
|
|
1567
|
-
this.defaultConfig = defaultConfig;
|
|
1568
|
-
}
|
|
1569
|
-
breakers = /* @__PURE__ */ new Map();
|
|
1570
|
-
globalListeners = /* @__PURE__ */ new Set();
|
|
1571
|
-
getBreaker(cliName, config) {
|
|
1572
|
-
let breaker = this.breakers.get(cliName);
|
|
1573
|
-
if (!breaker) {
|
|
1574
|
-
const mergedConfig = {
|
|
1575
|
-
...DEFAULT_CIRCUIT_BREAKER_CONFIG,
|
|
1576
|
-
...this.defaultConfig,
|
|
1577
|
-
...config
|
|
1578
|
-
};
|
|
1579
|
-
breaker = new CliCircuitBreaker(cliName, mergedConfig);
|
|
1580
|
-
for (const listener of this.globalListeners) {
|
|
1581
|
-
breaker.addStateChangeListener(listener);
|
|
1582
|
-
}
|
|
1583
|
-
this.breakers.set(cliName, breaker);
|
|
1584
|
-
}
|
|
1585
|
-
return breaker;
|
|
1586
|
-
}
|
|
1587
|
-
isOpen(cliName) {
|
|
1588
|
-
return this.breakers.get(cliName)?.getState() === "open";
|
|
1589
|
-
}
|
|
1590
|
-
getAllSnapshots() {
|
|
1591
|
-
const snapshots = /* @__PURE__ */ new Map();
|
|
1592
|
-
for (const [name, breaker] of this.breakers) {
|
|
1593
|
-
snapshots.set(name, breaker.getSnapshot());
|
|
1594
|
-
}
|
|
1595
|
-
return snapshots;
|
|
1596
|
-
}
|
|
1597
|
-
resetAll() {
|
|
1598
|
-
for (const breaker of this.breakers.values()) {
|
|
1599
|
-
breaker.reset();
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
reset(cliName) {
|
|
1603
|
-
this.breakers.get(cliName)?.reset();
|
|
1604
|
-
}
|
|
1605
|
-
addGlobalStateChangeListener(listener) {
|
|
1606
|
-
this.globalListeners.add(listener);
|
|
1607
|
-
for (const breaker of this.breakers.values()) {
|
|
1608
|
-
breaker.addStateChangeListener(listener);
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
removeGlobalStateChangeListener(listener) {
|
|
1612
|
-
this.globalListeners.delete(listener);
|
|
1613
|
-
for (const breaker of this.breakers.values()) {
|
|
1614
|
-
breaker.removeStateChangeListener(listener);
|
|
1615
|
-
}
|
|
1616
|
-
}
|
|
1617
|
-
getHealthyClis() {
|
|
1618
|
-
const healthy = [];
|
|
1619
|
-
for (const [name, breaker] of this.breakers) {
|
|
1620
|
-
if (breaker.getState() === "closed") {
|
|
1621
|
-
healthy.push(name);
|
|
1622
|
-
}
|
|
1623
|
-
}
|
|
1624
|
-
return healthy;
|
|
1625
|
-
}
|
|
1626
|
-
getUnhealthyClis() {
|
|
1627
|
-
const unhealthy = [];
|
|
1628
|
-
for (const [name, breaker] of this.breakers) {
|
|
1629
|
-
const state = breaker.getState();
|
|
1630
|
-
if (state === "open" || state === "half-open") {
|
|
1631
|
-
unhealthy.push(name);
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
1634
|
-
return unhealthy;
|
|
1635
|
-
}
|
|
1636
|
-
};
|
|
1637
|
-
function mapCliErrorToCategory(errorCode) {
|
|
1638
|
-
const mapping = {
|
|
1639
|
-
TIMEOUT: "timeout",
|
|
1640
|
-
NOT_AUTHENTICATED: "authentication",
|
|
1641
|
-
RATE_LIMITED: "rate_limit",
|
|
1642
|
-
CONNECTION_ERROR: "connection"
|
|
1643
|
-
};
|
|
1644
|
-
return mapping[errorCode] ?? "unknown";
|
|
1645
|
-
}
|
|
1646
|
-
|
|
1647
1392
|
// src/cli-adapters/adapters/gemini-adapter-helpers.ts
|
|
1648
1393
|
var GEMINI_LEGACY_DEFAULTS = {
|
|
1649
1394
|
displayNames: {},
|
|
@@ -1654,34 +1399,6 @@ var GEMINI_LEGACY_DEFAULTS = {
|
|
|
1654
1399
|
inputCost: 0.15,
|
|
1655
1400
|
outputCost: 0.6
|
|
1656
1401
|
};
|
|
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
1402
|
function createCircuitOpenError(cli) {
|
|
1686
1403
|
return {
|
|
1687
1404
|
code: "EXECUTION_ERROR",
|
|
@@ -1833,69 +1550,14 @@ var GeminiCliAdapter = class extends SubprocessCliAdapter {
|
|
|
1833
1550
|
return err(result.error);
|
|
1834
1551
|
}
|
|
1835
1552
|
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)
|
|
1553
|
+
return executeCliRetryLoop(() => this.executeTask(task, options), {
|
|
1554
|
+
maxRetries: options.maxRetries,
|
|
1555
|
+
allowRetry: options.allowRetry,
|
|
1556
|
+
baseDelayMs: this.baseDelayMs,
|
|
1557
|
+
maxDelayMs: this.maxDelayMs,
|
|
1558
|
+
circuitBreaker: this.circuitBreaker,
|
|
1559
|
+
cli: this.name,
|
|
1560
|
+
logger: this.adapterLogger
|
|
1899
1561
|
});
|
|
1900
1562
|
}
|
|
1901
1563
|
};
|
|
@@ -2933,9 +2595,9 @@ function createCodexAdapter(transport, options) {
|
|
|
2933
2595
|
}
|
|
2934
2596
|
return new CodexMcpAdapter(options);
|
|
2935
2597
|
}
|
|
2936
|
-
function createAllAdapters(
|
|
2598
|
+
function createAllAdapters(logger5, codexTransport = "mcp") {
|
|
2937
2599
|
const adapters = /* @__PURE__ */ new Map();
|
|
2938
|
-
const options =
|
|
2600
|
+
const options = logger5 !== void 0 ? { logger: logger5 } : void 0;
|
|
2939
2601
|
adapters.set("claude", new ClaudeCliAdapter(options));
|
|
2940
2602
|
adapters.set("gemini", new GeminiCliAdapter(options));
|
|
2941
2603
|
adapters.set("codex", createCodexAdapter(codexTransport, options ?? {}));
|
|
@@ -2979,920 +2641,7 @@ async function getAvailableClis(cache) {
|
|
|
2979
2641
|
).map((r) => r.value.cli);
|
|
2980
2642
|
}
|
|
2981
2643
|
|
|
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
|
-
writeLine(` ${colors.dim}Run: npx tsx scripts/probe-models.ts${colors.reset}`);
|
|
3405
|
-
} else {
|
|
3406
|
-
writeLine(`${formatStatus(true)} Model registry: ${ageText}`);
|
|
3407
|
-
}
|
|
3408
|
-
}
|
|
3409
|
-
function printLearningPersistence(check) {
|
|
3410
|
-
if (!check.enabled) {
|
|
3411
|
-
writeLine(`${formatStatus(true)} Learning persistence: ${colors.dim}Disabled${colors.reset}`);
|
|
3412
|
-
writeLine(` ${colors.dim}Set NEXUS_PERSIST_LEARNING=true to enable${colors.reset}`);
|
|
3413
|
-
return;
|
|
3414
|
-
}
|
|
3415
|
-
const healthy = check.dirExists && check.dirWritable && check.error === null;
|
|
3416
|
-
writeLine(`${formatStatus(healthy, !healthy)} Learning persistence: Enabled`);
|
|
3417
|
-
if (check.error !== null) {
|
|
3418
|
-
writeLine(` ${colors.red}Error: ${check.error}${colors.reset}`);
|
|
3419
|
-
return;
|
|
3420
|
-
}
|
|
3421
|
-
const dirStatus = check.dirExists ? check.dirWritable ? `${colors.green}writable${colors.reset}` : `${colors.red}not writable${colors.reset}` : `${colors.yellow}not created yet${colors.reset}`;
|
|
3422
|
-
writeLine(` Data directory: ${dirStatus}`);
|
|
3423
|
-
writeLine(` Outcomes: ${String(check.outcomeCount)} recorded`);
|
|
3424
|
-
writeLine(` Distilled rules: ${String(check.ruleCount)} active`);
|
|
3425
|
-
if (check.rulesLastSaved !== null) {
|
|
3426
|
-
writeLine(` Rules last saved: ${check.rulesLastSaved}`);
|
|
3427
|
-
}
|
|
3428
|
-
}
|
|
3429
|
-
function printSqliteCheck(check) {
|
|
3430
|
-
if (check.available) {
|
|
3431
|
-
writeLine(
|
|
3432
|
-
`${formatStatus(true)} SQLite (better-sqlite3): ${colors.green}Available${colors.reset}`
|
|
3433
|
-
);
|
|
3434
|
-
} else {
|
|
3435
|
-
writeLine(
|
|
3436
|
-
`${formatStatus(false, true)} SQLite (better-sqlite3): ${colors.yellow}Not available${colors.reset}`
|
|
3437
|
-
);
|
|
3438
|
-
writeLine(
|
|
3439
|
-
` ${colors.dim}Memory backends (agentic, adaptive, typed) require it${colors.reset}`
|
|
3440
|
-
);
|
|
3441
|
-
writeLine(` ${colors.dim}Fix: npm install -g better-sqlite3${colors.reset}`);
|
|
3442
|
-
}
|
|
3443
|
-
}
|
|
3444
|
-
function printDataDirectory(check) {
|
|
3445
|
-
if (check.rootExists) {
|
|
3446
|
-
const existCount = check.subdirectories.filter((d) => d.exists).length;
|
|
3447
|
-
const totalCount = check.subdirectories.length;
|
|
3448
|
-
const allExist = existCount === totalCount;
|
|
3449
|
-
const allWritable = check.subdirectories.every((d) => !d.exists || d.writable);
|
|
3450
|
-
const healthy = allExist && allWritable;
|
|
3451
|
-
writeLine(
|
|
3452
|
-
`${formatStatus(healthy, !healthy)} Data directory: ${check.rootPath} (${String(existCount)}/${String(totalCount)} subdirs)`
|
|
3453
|
-
);
|
|
3454
|
-
if (!allExist) {
|
|
3455
|
-
const missing = check.subdirectories.filter((d) => !d.exists);
|
|
3456
|
-
for (const dir of missing) {
|
|
3457
|
-
writeLine(` ${colors.dim}Missing: ${dir.name}/${colors.reset}`);
|
|
3458
|
-
}
|
|
3459
|
-
writeLine(` ${colors.dim}Fix: nexus-agents setup${colors.reset}`);
|
|
3460
|
-
}
|
|
3461
|
-
if (!allWritable) {
|
|
3462
|
-
const readonly_ = check.subdirectories.filter((d) => d.exists && !d.writable);
|
|
3463
|
-
for (const dir of readonly_) {
|
|
3464
|
-
writeLine(` ${colors.yellow}Not writable: ${dir.name}/${colors.reset}`);
|
|
3465
|
-
}
|
|
3466
|
-
}
|
|
3467
|
-
} else {
|
|
3468
|
-
writeLine(
|
|
3469
|
-
`${formatStatus(false, true)} Data directory: ${colors.yellow}Not created${colors.reset}`
|
|
3470
|
-
);
|
|
3471
|
-
writeLine(` ${colors.dim}Run: nexus-agents setup${colors.reset}`);
|
|
3472
|
-
}
|
|
3473
|
-
}
|
|
3474
|
-
function printDoctorSummary(result) {
|
|
3475
|
-
const unhealthyCount = result.clis.filter((c) => !c.installed || !c.authenticated).length;
|
|
3476
|
-
const nodeIssue = result.nodeVersion.supported ? 0 : 1;
|
|
3477
|
-
const totalIssues = unhealthyCount + nodeIssue + (result.mcpServerReady ? 0 : 1);
|
|
3478
|
-
const summary = result.allHealthy ? `${colors.green}${colors.bold}Status: Ready${colors.reset}` : `${colors.yellow}${colors.bold}Summary: ${String(totalIssues)} issue(s) found${colors.reset}`;
|
|
3479
|
-
writeLine(summary);
|
|
3480
|
-
writeLine("");
|
|
3481
|
-
}
|
|
3482
|
-
function printDoctorResults(result) {
|
|
3483
|
-
writeLine("");
|
|
3484
|
-
writeLine(`${colors.bold}Nexus Agents Doctor${colors.reset}`);
|
|
3485
|
-
writeLine("===================");
|
|
3486
|
-
writeLine("");
|
|
3487
|
-
writeLine(`${colors.cyan}Checking environment...${colors.reset}`);
|
|
3488
|
-
writeLine("");
|
|
3489
|
-
printNodeVersionCheck(result.nodeVersion);
|
|
3490
|
-
printApiKeysCheck(result.apiKeys);
|
|
3491
|
-
printConfigFileCheck(result.configFile);
|
|
3492
|
-
writeLine("");
|
|
3493
|
-
writeLine(`${colors.cyan}Checking CLI installations...${colors.reset}`);
|
|
3494
|
-
writeLine("");
|
|
3495
|
-
for (const cli of result.clis) {
|
|
3496
|
-
printCliResult(cli);
|
|
3497
|
-
}
|
|
3498
|
-
writeLine(`${colors.cyan}Checking MCP configuration...${colors.reset}`);
|
|
3499
|
-
writeLine("");
|
|
3500
|
-
writeLine(
|
|
3501
|
-
`${formatStatus(result.mcpServerReady)} MCP Server mode: ${result.mcpServerReady ? "Ready" : "Not ready"}`
|
|
3502
|
-
);
|
|
3503
|
-
writeLine(
|
|
3504
|
-
`${formatStatus(result.mcpClientReady)} MCP Client mode: ${result.mcpClientReady ? "Ready (Codex mcp-server)" : "Not ready (Codex not installed)"}`
|
|
3505
|
-
);
|
|
3506
|
-
writeLine("");
|
|
3507
|
-
writeLine(`${colors.cyan}Checking capabilities...${colors.reset}`);
|
|
3508
|
-
writeLine("");
|
|
3509
|
-
printCapabilities(result.clis);
|
|
3510
|
-
writeLine("");
|
|
3511
|
-
writeLine(`${colors.cyan}Checking model registry...${colors.reset}`);
|
|
3512
|
-
writeLine("");
|
|
3513
|
-
printRegistryAdvisory(result.registryAdvisory);
|
|
3514
|
-
writeLine("");
|
|
3515
|
-
writeLine(`${colors.cyan}Checking learning subsystem...${colors.reset}`);
|
|
3516
|
-
writeLine("");
|
|
3517
|
-
printLearningPersistence(result.learningPersistence);
|
|
3518
|
-
writeLine("");
|
|
3519
|
-
writeLine(`${colors.cyan}Checking data storage...${colors.reset}`);
|
|
3520
|
-
writeLine("");
|
|
3521
|
-
printSqliteCheck(result.sqliteCheck);
|
|
3522
|
-
printDataDirectory(result.dataDirectory);
|
|
3523
|
-
writeLine("");
|
|
3524
|
-
printDoctorSummary(result);
|
|
3525
|
-
}
|
|
3526
|
-
|
|
3527
|
-
// src/cli/doctor.ts
|
|
3528
|
-
var REQUIRED_NODE_MAJOR2 = 22;
|
|
3529
|
-
var API_KEY_VARS = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GOOGLE_AI_API_KEY"];
|
|
3530
|
-
var CONFIG_FILE_PATHS = ["./nexus-agents.yaml", "./nexus-agents.yml"];
|
|
3531
|
-
var DATA_SUBDIRECTORIES = [
|
|
3532
|
-
"memory",
|
|
3533
|
-
"memory/beliefs",
|
|
3534
|
-
"learning",
|
|
3535
|
-
"sessions",
|
|
3536
|
-
"audit",
|
|
3537
|
-
"voting",
|
|
3538
|
-
"auth",
|
|
3539
|
-
"research",
|
|
3540
|
-
"checkpoints"
|
|
3541
|
-
];
|
|
3542
|
-
function getFixCommand(name, issue) {
|
|
3543
|
-
const commands = {
|
|
3544
|
-
claude: {
|
|
3545
|
-
install: "npm install -g @anthropic-ai/claude-code",
|
|
3546
|
-
upgrade: "npm update -g @anthropic-ai/claude-code",
|
|
3547
|
-
auth: "claude auth login"
|
|
3548
|
-
},
|
|
3549
|
-
gemini: {
|
|
3550
|
-
install: "npm install -g @google/gemini-cli",
|
|
3551
|
-
upgrade: "npm update -g @google/gemini-cli",
|
|
3552
|
-
auth: "gemini auth login"
|
|
3553
|
-
},
|
|
3554
|
-
codex: {
|
|
3555
|
-
install: "npm install -g @openai/codex",
|
|
3556
|
-
upgrade: "npm update -g @openai/codex",
|
|
3557
|
-
auth: "codex auth login"
|
|
3558
|
-
},
|
|
3559
|
-
opencode: {
|
|
3560
|
-
install: "npm install -g opencode-ai",
|
|
3561
|
-
upgrade: "npm update -g opencode-ai",
|
|
3562
|
-
auth: "opencode auth login"
|
|
3563
|
-
}
|
|
3564
|
-
};
|
|
3565
|
-
return commands[name][issue] ?? "";
|
|
3566
|
-
}
|
|
3567
|
-
function createNotFoundResult(name, errorMsg) {
|
|
3568
|
-
return {
|
|
3569
|
-
name,
|
|
3570
|
-
installed: false,
|
|
3571
|
-
version: "N/A",
|
|
3572
|
-
versionStatus: "unsupported",
|
|
3573
|
-
authenticated: false,
|
|
3574
|
-
error: errorMsg,
|
|
3575
|
-
fix: getFixCommand(name, "install")
|
|
3576
|
-
};
|
|
3577
|
-
}
|
|
3578
|
-
function detectAuthMethod(name) {
|
|
3579
|
-
const authMethods = {
|
|
3580
|
-
claude: "CLI auth",
|
|
3581
|
-
gemini: "ADC/CLI auth",
|
|
3582
|
-
codex: "CLI auth",
|
|
3583
|
-
opencode: "CLI auth"
|
|
3584
|
-
};
|
|
3585
|
-
return authMethods[name];
|
|
3586
|
-
}
|
|
3587
|
-
function createHealthyResult(name, health, capacity) {
|
|
3588
|
-
const authenticated = health.healthy;
|
|
3589
|
-
const result = {
|
|
3590
|
-
name,
|
|
3591
|
-
installed: true,
|
|
3592
|
-
version: health.version,
|
|
3593
|
-
versionStatus: health.versionStatus,
|
|
3594
|
-
authenticated,
|
|
3595
|
-
...authenticated && { authMethod: detectAuthMethod(name) },
|
|
3596
|
-
...capacity !== void 0 && { capacity }
|
|
3597
|
-
};
|
|
3598
|
-
if (health.message !== void 0 && health.message !== "") {
|
|
3599
|
-
return { ...result, error: health.message };
|
|
3600
|
-
}
|
|
3601
|
-
if (!authenticated) {
|
|
3602
|
-
return { ...result, fix: getFixCommand(name, "auth") };
|
|
3603
|
-
}
|
|
3604
|
-
if (health.versionStatus === "outdated") {
|
|
3605
|
-
return { ...result, fix: getFixCommand(name, "upgrade") };
|
|
3606
|
-
}
|
|
3607
|
-
return result;
|
|
3608
|
-
}
|
|
3609
|
-
async function checkCli(name) {
|
|
3610
|
-
const adapters = createAllAdapters();
|
|
3611
|
-
const adapter = adapters.get(name);
|
|
3612
|
-
if (!adapter) {
|
|
3613
|
-
return createNotFoundResult(name, "Adapter not available");
|
|
3614
|
-
}
|
|
3615
|
-
try {
|
|
3616
|
-
const health = await adapter.healthCheck();
|
|
3617
|
-
let capacity;
|
|
3618
|
-
try {
|
|
3619
|
-
capacity = await adapter.getCapacity();
|
|
3620
|
-
} catch (capErr) {
|
|
3621
|
-
void capErr;
|
|
3622
|
-
}
|
|
3623
|
-
return createHealthyResult(name, health, capacity);
|
|
3624
|
-
} catch (error) {
|
|
3625
|
-
const message = getErrorMessage(error);
|
|
3626
|
-
const isNotFound = message.includes("ENOENT") || message.includes("not found");
|
|
3627
|
-
return createNotFoundResult(name, isNotFound ? "Not found in PATH" : message);
|
|
3628
|
-
}
|
|
3629
|
-
}
|
|
3630
|
-
function checkNodeVersion() {
|
|
3631
|
-
const version = process.version;
|
|
3632
|
-
const major = Number(version.slice(1).split(".")[0]);
|
|
3633
|
-
return {
|
|
3634
|
-
version,
|
|
3635
|
-
major,
|
|
3636
|
-
supported: major >= REQUIRED_NODE_MAJOR2
|
|
3637
|
-
};
|
|
3638
|
-
}
|
|
3639
|
-
function checkApiKeys() {
|
|
3640
|
-
return API_KEY_VARS.map((name) => ({
|
|
3641
|
-
name,
|
|
3642
|
-
configured: typeof process.env[name] === "string" && process.env[name] !== ""
|
|
3643
|
-
}));
|
|
3644
|
-
}
|
|
3645
|
-
function checkConfigFile() {
|
|
3646
|
-
for (const configPath of CONFIG_FILE_PATHS) {
|
|
3647
|
-
if (existsSync(configPath)) {
|
|
3648
|
-
return { found: true, path: configPath };
|
|
3649
|
-
}
|
|
3650
|
-
}
|
|
3651
|
-
return { found: false, path: null };
|
|
3652
|
-
}
|
|
3653
|
-
function checkMcpServerReady() {
|
|
3654
|
-
try {
|
|
3655
|
-
const result = createServer({ name: "nexus-agents-doctor-check" });
|
|
3656
|
-
return result.ok;
|
|
3657
|
-
} catch {
|
|
3658
|
-
return false;
|
|
3659
|
-
}
|
|
3660
|
-
}
|
|
3661
|
-
function buildRegistryAdvisory(cliResults) {
|
|
3662
|
-
const installedClis = new Set(cliResults.filter((c) => c.installed).map((c) => c.name));
|
|
3663
|
-
const models = DEFAULT_MODEL_CAPABILITIES.models.filter((m) => m.cliName !== void 0).map((m) => {
|
|
3664
|
-
const cliName = m.cliName ?? "";
|
|
3665
|
-
const available = cliName.length > 0 && installedClis.has(cliName);
|
|
3666
|
-
const reason = available ? `${cliName} CLI is installed` : `${cliName} CLI is not installed`;
|
|
3667
|
-
return { modelId: m.id, displayName: m.displayName, cliName, available, reason };
|
|
3668
|
-
});
|
|
3669
|
-
const STALE_THRESHOLD_DAYS = 30;
|
|
3670
|
-
const updatedAt = new Date(DEFAULT_MODEL_CAPABILITIES.updatedAt);
|
|
3671
|
-
const nowMs = getTimeProvider().now();
|
|
3672
|
-
const ageDays = Math.floor((nowMs - updatedAt.getTime()) / (1e3 * 60 * 60 * 24));
|
|
3673
|
-
return {
|
|
3674
|
-
totalModels: models.length,
|
|
3675
|
-
availableModels: models.filter((m) => m.available).length,
|
|
3676
|
-
unavailableModels: models.filter((m) => !m.available).length,
|
|
3677
|
-
models,
|
|
3678
|
-
registryAgeDays: ageDays,
|
|
3679
|
-
registryStale: ageDays > STALE_THRESHOLD_DAYS
|
|
3680
|
-
};
|
|
3681
|
-
}
|
|
3682
|
-
function countJsonlLines(filePath) {
|
|
3683
|
-
if (!existsSync(filePath)) return 0;
|
|
3684
|
-
return readFileSync(filePath, "utf-8").split("\n").filter((l) => l.trim().length > 0).length;
|
|
3685
|
-
}
|
|
3686
|
-
function readRulesMetadata(filePath) {
|
|
3687
|
-
if (!existsSync(filePath)) return { count: 0, savedAt: null };
|
|
3688
|
-
try {
|
|
3689
|
-
const raw = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
3690
|
-
const rules = raw["rules"];
|
|
3691
|
-
const saved = raw["savedAt"];
|
|
3692
|
-
return {
|
|
3693
|
-
count: Array.isArray(rules) ? rules.length : 0,
|
|
3694
|
-
savedAt: typeof saved === "string" ? saved : null
|
|
3695
|
-
};
|
|
3696
|
-
} catch {
|
|
3697
|
-
return { count: 0, savedAt: null };
|
|
3698
|
-
}
|
|
3699
|
-
}
|
|
3700
|
-
function checkDirAccess(dir) {
|
|
3701
|
-
const exists = existsSync(dir);
|
|
3702
|
-
if (!exists) return { exists: false, writable: false };
|
|
3703
|
-
try {
|
|
3704
|
-
accessSync(dir, fsConstants.W_OK);
|
|
3705
|
-
return { exists: true, writable: true };
|
|
3706
|
-
} catch {
|
|
3707
|
-
return { exists: true, writable: false };
|
|
3708
|
-
}
|
|
3709
|
-
}
|
|
3710
|
-
var DISABLED_CHECK = {
|
|
3711
|
-
enabled: false,
|
|
3712
|
-
dirExists: false,
|
|
3713
|
-
dirWritable: false,
|
|
3714
|
-
outcomeCount: 0,
|
|
3715
|
-
ruleCount: 0,
|
|
3716
|
-
rulesLastSaved: null,
|
|
3717
|
-
error: null
|
|
3718
|
-
};
|
|
3719
|
-
function checkLearningPersistence() {
|
|
3720
|
-
if (!isPersistenceEnabled()) return DISABLED_CHECK;
|
|
3721
|
-
try {
|
|
3722
|
-
const { exists: dirExists, writable: dirWritable } = checkDirAccess(LEARNING_DIR);
|
|
3723
|
-
const outcomeCount = countJsonlLines(OUTCOMES_FILE);
|
|
3724
|
-
const { count: ruleCount, savedAt: rulesLastSaved } = readRulesMetadata(RULES_FILE);
|
|
3725
|
-
return {
|
|
3726
|
-
enabled: true,
|
|
3727
|
-
dirExists,
|
|
3728
|
-
dirWritable,
|
|
3729
|
-
outcomeCount,
|
|
3730
|
-
ruleCount,
|
|
3731
|
-
rulesLastSaved,
|
|
3732
|
-
error: null
|
|
3733
|
-
};
|
|
3734
|
-
} catch (error) {
|
|
3735
|
-
return {
|
|
3736
|
-
enabled: true,
|
|
3737
|
-
dirExists: false,
|
|
3738
|
-
dirWritable: false,
|
|
3739
|
-
outcomeCount: 0,
|
|
3740
|
-
ruleCount: 0,
|
|
3741
|
-
rulesLastSaved: null,
|
|
3742
|
-
error: getErrorMessage(error)
|
|
3743
|
-
};
|
|
3744
|
-
}
|
|
3745
|
-
}
|
|
3746
|
-
async function checkSqlite() {
|
|
3747
|
-
try {
|
|
3748
|
-
await import("better-sqlite3");
|
|
3749
|
-
return { available: true, error: null };
|
|
3750
|
-
} catch (error) {
|
|
3751
|
-
const msg = getErrorMessage(error);
|
|
3752
|
-
const isNotFound = msg.includes("Cannot find") || msg.includes("MODULE_NOT_FOUND");
|
|
3753
|
-
return {
|
|
3754
|
-
available: false,
|
|
3755
|
-
error: isNotFound ? "better-sqlite3 not installed \u2014 5 memory backends unavailable" : `better-sqlite3 load error: ${msg}`
|
|
3756
|
-
};
|
|
3757
|
-
}
|
|
3758
|
-
}
|
|
3759
|
-
function checkDataDirectory() {
|
|
3760
|
-
const rootPath = join(homedir(), ".nexus-agents");
|
|
3761
|
-
const rootExists = existsSync(rootPath);
|
|
3762
|
-
const subdirectories = DATA_SUBDIRECTORIES.map((name) => {
|
|
3763
|
-
const fullPath = join(rootPath, name);
|
|
3764
|
-
const exists = existsSync(fullPath);
|
|
3765
|
-
return { name, path: fullPath, exists, writable: exists && isWritable(fullPath) };
|
|
3766
|
-
});
|
|
3767
|
-
return { rootExists, rootPath, subdirectories };
|
|
3768
|
-
}
|
|
3769
|
-
function isWritable(dirPath) {
|
|
3770
|
-
try {
|
|
3771
|
-
accessSync(dirPath, fsConstants.W_OK);
|
|
3772
|
-
return true;
|
|
3773
|
-
} catch {
|
|
3774
|
-
return false;
|
|
3775
|
-
}
|
|
3776
|
-
}
|
|
3777
|
-
async function runDoctor() {
|
|
3778
|
-
const clis = await Promise.all([
|
|
3779
|
-
checkCli("claude"),
|
|
3780
|
-
checkCli("gemini"),
|
|
3781
|
-
checkCli("codex"),
|
|
3782
|
-
checkCli("opencode")
|
|
3783
|
-
]);
|
|
3784
|
-
const nodeVersion = checkNodeVersion();
|
|
3785
|
-
const apiKeys = checkApiKeys();
|
|
3786
|
-
const configFile = checkConfigFile();
|
|
3787
|
-
const mcpServerReady = checkMcpServerReady();
|
|
3788
|
-
const codexCheck = clis.find((c) => c.name === "codex");
|
|
3789
|
-
const mcpClientReady = codexCheck?.installed ?? false;
|
|
3790
|
-
const registryAdvisory = buildRegistryAdvisory(clis);
|
|
3791
|
-
const learningPersistence = checkLearningPersistence();
|
|
3792
|
-
const sqliteCheck = await checkSqlite();
|
|
3793
|
-
const dataDirectory = checkDataDirectory();
|
|
3794
|
-
const hasAuthMethod = apiKeys.some((k) => k.configured) || clis.some((c) => c.installed && c.authenticated);
|
|
3795
|
-
const allHealthy = nodeVersion.supported && hasAuthMethod && mcpServerReady && clis.every((c) => c.installed && c.authenticated && c.versionStatus !== "unsupported");
|
|
3796
|
-
return {
|
|
3797
|
-
clis,
|
|
3798
|
-
nodeVersion,
|
|
3799
|
-
apiKeys,
|
|
3800
|
-
configFile,
|
|
3801
|
-
mcpServerReady,
|
|
3802
|
-
mcpClientReady,
|
|
3803
|
-
registryAdvisory,
|
|
3804
|
-
learningPersistence,
|
|
3805
|
-
sqliteCheck,
|
|
3806
|
-
dataDirectory,
|
|
3807
|
-
allHealthy,
|
|
3808
|
-
timestamp: new Date(getTimeProvider().now())
|
|
3809
|
-
};
|
|
3810
|
-
}
|
|
3811
|
-
async function doctorCommand(options = {}) {
|
|
3812
|
-
const result = await runDoctor();
|
|
3813
|
-
printDoctorResults(result);
|
|
3814
|
-
if (options.fix === true) {
|
|
3815
|
-
await runDoctorFix(result);
|
|
3816
|
-
}
|
|
3817
|
-
return result.allHealthy ? 0 : 1;
|
|
3818
|
-
}
|
|
3819
|
-
async function runDoctorFix(result) {
|
|
3820
|
-
const writeLine2 = (text) => {
|
|
3821
|
-
process.stdout.write(text + "\n");
|
|
3822
|
-
};
|
|
3823
|
-
writeLine2("");
|
|
3824
|
-
writeLine2("\x1B[1mAuto-fix\x1B[0m");
|
|
3825
|
-
writeLine2("\u2500".repeat(40));
|
|
3826
|
-
let fixCount = 0;
|
|
3827
|
-
if (!result.dataDirectory.rootExists || result.dataDirectory.subdirectories.some((d) => !d.exists || !d.writable)) {
|
|
3828
|
-
const { runSetup } = await import("./setup-command-SS7LMN7Y.js");
|
|
3829
|
-
const setupResult = runSetup({
|
|
3830
|
-
skipMcp: true,
|
|
3831
|
-
skipRules: true,
|
|
3832
|
-
skipHooks: true,
|
|
3833
|
-
skipConfig: true,
|
|
3834
|
-
skipOpencode: true
|
|
3835
|
-
});
|
|
3836
|
-
if (setupResult.success) {
|
|
3837
|
-
writeLine2("\u2713 Created missing data directories");
|
|
3838
|
-
fixCount++;
|
|
3839
|
-
}
|
|
3840
|
-
}
|
|
3841
|
-
if (!result.configFile.found) {
|
|
3842
|
-
const { runConfigInitSync } = await import("./setup-config-DSMOOLVW.js");
|
|
3843
|
-
const configResult = runConfigInitSync(process.cwd(), false, false);
|
|
3844
|
-
if (configResult.success && configResult.created) {
|
|
3845
|
-
writeLine2(`\u2713 Generated config: ${configResult.path}`);
|
|
3846
|
-
fixCount++;
|
|
3847
|
-
}
|
|
3848
|
-
}
|
|
3849
|
-
if (!result.sqliteCheck.available) {
|
|
3850
|
-
writeLine2("");
|
|
3851
|
-
writeLine2("\u26A0 better-sqlite3 not installed (manual step required):");
|
|
3852
|
-
writeLine2(" npm install -g better-sqlite3");
|
|
3853
|
-
}
|
|
3854
|
-
if (fixCount > 0) {
|
|
3855
|
-
writeLine2("");
|
|
3856
|
-
writeLine2(
|
|
3857
|
-
`\x1B[32m${String(fixCount)} issue(s) fixed.\x1B[0m Re-run \x1B[1mnexus-agents doctor\x1B[0m to verify.`
|
|
3858
|
-
);
|
|
3859
|
-
} else {
|
|
3860
|
-
writeLine2("No auto-fixable issues found.");
|
|
3861
|
-
}
|
|
3862
|
-
writeLine2("");
|
|
3863
|
-
}
|
|
3864
|
-
|
|
3865
|
-
// src/cli/setup-data-dir.ts
|
|
3866
|
-
var NEXUS_DATA_DIR = join2(homedir2(), ".nexus-agents");
|
|
3867
|
-
var RESTRICTED_DIRS = /* @__PURE__ */ new Set(["auth"]);
|
|
3868
|
-
function initDataDirectories(dryRun = false) {
|
|
3869
|
-
const created = [];
|
|
3870
|
-
const alreadyExisted = [];
|
|
3871
|
-
try {
|
|
3872
|
-
ensureDir(NEXUS_DATA_DIR, dryRun, created, alreadyExisted);
|
|
3873
|
-
for (const subdir of DATA_SUBDIRECTORIES) {
|
|
3874
|
-
const mode = RESTRICTED_DIRS.has(subdir) ? 448 : void 0;
|
|
3875
|
-
ensureDir(join2(NEXUS_DATA_DIR, subdir), dryRun, created, alreadyExisted, mode);
|
|
3876
|
-
}
|
|
3877
|
-
return { success: true, rootPath: NEXUS_DATA_DIR, created, alreadyExisted, error: null };
|
|
3878
|
-
} catch (error) {
|
|
3879
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
3880
|
-
return { success: false, rootPath: NEXUS_DATA_DIR, created, alreadyExisted, error: msg };
|
|
3881
|
-
}
|
|
3882
|
-
}
|
|
3883
|
-
function ensureDir(dirPath, dryRun, created, alreadyExisted, mode) {
|
|
3884
|
-
if (existsSync2(dirPath)) {
|
|
3885
|
-
alreadyExisted.push(dirPath);
|
|
3886
|
-
return;
|
|
3887
|
-
}
|
|
3888
|
-
if (!dryRun) {
|
|
3889
|
-
mkdirSync(dirPath, { recursive: true, ...mode !== void 0 ? { mode } : {} });
|
|
3890
|
-
}
|
|
3891
|
-
created.push(dirPath);
|
|
3892
|
-
}
|
|
3893
|
-
|
|
3894
2644
|
export {
|
|
3895
|
-
VERSION,
|
|
3896
2645
|
sleep,
|
|
3897
2646
|
delay,
|
|
3898
2647
|
withTimeout,
|
|
@@ -3901,6 +2650,10 @@ export {
|
|
|
3901
2650
|
getTimeoutForTask,
|
|
3902
2651
|
estimateTaskComplexity2 as estimateTaskComplexity,
|
|
3903
2652
|
getTimeoutForTaskAuto,
|
|
2653
|
+
calculateBackoffDelay,
|
|
2654
|
+
isRetryableError,
|
|
2655
|
+
categorizeError,
|
|
2656
|
+
executeCliRetryLoop,
|
|
3904
2657
|
BaseCliAdapter,
|
|
3905
2658
|
SubprocessCliAdapter,
|
|
3906
2659
|
asRecord,
|
|
@@ -3908,9 +2661,6 @@ export {
|
|
|
3908
2661
|
extractNumberField,
|
|
3909
2662
|
ClaudeResponseParser,
|
|
3910
2663
|
ClaudeCliAdapter,
|
|
3911
|
-
CircuitError,
|
|
3912
|
-
CircuitBreakerRegistry,
|
|
3913
|
-
mapCliErrorToCategory,
|
|
3914
2664
|
GeminiCliAdapter,
|
|
3915
2665
|
CodexResponseParser,
|
|
3916
2666
|
CodexCliAdapter,
|
|
@@ -3923,23 +2673,6 @@ export {
|
|
|
3923
2673
|
createCliAdapter,
|
|
3924
2674
|
createAllAdapters,
|
|
3925
2675
|
isCliAvailable,
|
|
3926
|
-
getAvailableClis
|
|
3927
|
-
STOPWORDS,
|
|
3928
|
-
tokenize,
|
|
3929
|
-
tokenizeFiltered,
|
|
3930
|
-
stringifyValue,
|
|
3931
|
-
capitalize,
|
|
3932
|
-
capitalizeWords,
|
|
3933
|
-
capitalizeKebab,
|
|
3934
|
-
truncateWithInfo,
|
|
3935
|
-
truncateSentence,
|
|
3936
|
-
DEFAULT_TASK_TTL_MS,
|
|
3937
|
-
clampTaskTtl,
|
|
3938
|
-
doctorCommand,
|
|
3939
|
-
initDataDirectories,
|
|
3940
|
-
createServer,
|
|
3941
|
-
connectTransport,
|
|
3942
|
-
startStdioServer,
|
|
3943
|
-
closeServer
|
|
2676
|
+
getAvailableClis
|
|
3944
2677
|
};
|
|
3945
|
-
//# sourceMappingURL=chunk-
|
|
2678
|
+
//# sourceMappingURL=chunk-4AGPR6XZ.js.map
|