nexus-agents 4.14.0 → 4.14.2
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/dist/{child-mcp-config-OULJI7VH.js → child-mcp-config-FNVCS5MQ.js} +5 -5
- package/dist/{chunk-4WVGF3SL.js → chunk-2RSNO5SP.js} +6 -6
- package/dist/{chunk-B6C3JCQB.js → chunk-6JEPV4XW.js} +2 -2
- package/dist/{chunk-VPTDFOAK.js → chunk-BLJY4YKP.js} +3 -3
- package/dist/{chunk-YHY5AZL2.js → chunk-CHRJKE7S.js} +2 -2
- package/dist/{chunk-LUBCO6ZN.js → chunk-CPRPZPX7.js} +2 -2
- package/dist/{chunk-IZUT5OHH.js → chunk-E536V52C.js} +2 -2
- package/dist/{chunk-IZUT5OHH.js.map → chunk-E536V52C.js.map} +1 -1
- package/dist/{chunk-56LJJOBN.js → chunk-ED47MBY5.js} +4 -4
- package/dist/{chunk-XWIFBBBH.js → chunk-EEJFMRHJ.js} +2 -2
- package/dist/{chunk-NMNTYAV2.js → chunk-FFMDYBGA.js} +3 -3
- package/dist/{chunk-V253VL7S.js → chunk-GEOALLHA.js} +4 -4
- package/dist/{chunk-F6SRPGDK.js → chunk-GFCCNGI7.js} +9 -9
- package/dist/{chunk-P45XNRHB.js → chunk-GT5B35X3.js} +2 -2
- package/dist/{chunk-ARTQO7XZ.js → chunk-H7LRUN27.js} +6 -6
- package/dist/{chunk-2HUC6NZN.js → chunk-HHLCAWAV.js} +2 -2
- package/dist/{chunk-HBF7O6CN.js → chunk-HJK5RZBX.js} +6 -6
- package/dist/{chunk-J6JEW4QZ.js → chunk-JD4HTIMG.js} +7 -7
- package/dist/{chunk-6454KSCM.js → chunk-JPA3GAWT.js} +2 -2
- package/dist/{chunk-XIIUIBSX.js → chunk-KGFFEQTN.js} +2 -2
- package/dist/{chunk-WATNLQS7.js → chunk-L6QLJ4VK.js} +2 -2
- package/dist/{chunk-EHMMTCCS.js → chunk-LLVEIPOZ.js} +8 -10
- package/dist/{chunk-EHMMTCCS.js.map → chunk-LLVEIPOZ.js.map} +1 -1
- package/dist/{chunk-232RDSCM.js → chunk-LWR2K2VM.js} +2 -2
- package/dist/{chunk-F6MUN523.js → chunk-MU7J7D5Q.js} +2 -2
- package/dist/{chunk-OQLFRJCW.js → chunk-NBMWONHE.js} +53 -53
- package/dist/chunk-NBMWONHE.js.map +1 -0
- package/dist/{chunk-VENIFAMX.js → chunk-NOROX25U.js} +4 -4
- package/dist/{chunk-7STKQ3IH.js → chunk-NX5VK5NW.js} +2 -2
- package/dist/{chunk-SNJNRDKZ.js → chunk-RQV5ZUXT.js} +2 -2
- package/dist/{chunk-KUGBMQAP.js → chunk-SQUEE5ZA.js} +4 -4
- package/dist/{chunk-W6IZVEIM.js → chunk-T5WQAYX2.js} +2 -2
- package/dist/{chunk-VZVGT4MN.js → chunk-UZRVS7MK.js} +5 -5
- package/dist/{chunk-PWSW5Z4L.js → chunk-VE3PAJ5M.js} +20 -10
- package/dist/chunk-VE3PAJ5M.js.map +1 -0
- package/dist/{chunk-TANMACVQ.js → chunk-VFGP57F5.js} +2 -2
- package/dist/{chunk-I6JMAM4J.js → chunk-VLDYF7EQ.js} +2 -2
- package/dist/{chunk-WPLXWTUK.js → chunk-W4Q5NZYZ.js} +5 -3
- package/dist/chunk-W4Q5NZYZ.js.map +1 -0
- package/dist/{chunk-TEWDRLCM.js → chunk-WCRXZJRW.js} +3426 -2208
- package/dist/chunk-WCRXZJRW.js.map +1 -0
- package/dist/{chunk-5PCTGUMF.js → chunk-WHOODKBB.js} +3 -3
- package/dist/chunk-WMSWVHFZ.js +1240 -0
- package/dist/chunk-WMSWVHFZ.js.map +1 -0
- package/dist/{chunk-XGCYJLTP.js → chunk-XT4J7QAO.js} +10 -10
- package/dist/{cli-circuit-breaker-4AVWA333.js → cli-circuit-breaker-A7GM4LXS.js} +5 -5
- package/dist/cli.js +2987 -1098
- package/dist/cli.js.map +1 -1
- package/dist/{composite-router-7OTZN7NS.js → composite-router-SVZA4OCQ.js} +4 -4
- package/dist/{consensus-vote-KFLDRUJJ.js → consensus-vote-7HGSTZKF.js} +20 -20
- package/dist/{context-retriever-2NZWXF6Z.js → context-retriever-QTASR42L.js} +11 -12
- package/dist/doctor-deep-NBDOSO4E.js +13 -0
- package/dist/{doctor-live-25LRXKBO.js → doctor-live-HZWIMIT6.js} +13 -13
- package/dist/{expert-bridge-2YLK3E6Q.js → expert-bridge-GY7RL4DF.js} +6 -6
- package/dist/factory-4FZ5FPZW.js +15 -0
- package/dist/factory-73NHOTVL.js +25 -0
- package/dist/{improvement-review-FFADRM4I.js → improvement-review-ZF3VHZEG.js} +7 -7
- package/dist/index.js +47 -48
- package/dist/index.js.map +1 -1
- package/dist/{init-opencode-OSZF74AL.js → init-opencode-EOCW7P4L.js} +8 -8
- package/dist/issue-triage-L6UDFHF4.js +17 -0
- package/dist/{learning-persistence-EGY4VKKO.js → learning-persistence-SR5F4DSY.js} +3 -3
- package/dist/{pr-reviewer-helpers-E3QXOVMT.js → pr-reviewer-helpers-KOE3X72Y.js} +6 -6
- package/dist/{registry-command-ZMKZ5CRZ.js → registry-command-7ZENMDK4.js} +4 -4
- package/dist/{repo-security-plan-BPET6KTP.js → repo-security-plan-PCHVMGHX.js} +5 -5
- package/dist/research-helpers-synthesize-KTVQVLII.js +14 -0
- package/dist/{routing-memory-3UWP6NOX.js → routing-memory-QTBLYGMC.js} +4 -4
- package/dist/{session-memory-N2ABITF7.js → session-memory-S7L4K5R5.js} +5 -5
- package/dist/setup-config-7SWEJXN4.js +11 -0
- package/dist/{setup-custom-api-PE2M5HWL.js → setup-custom-api-WYXYQMQF.js} +6 -6
- package/dist/setup-data-dir-EMXRXEFQ.js +28 -0
- package/dist/{tool-memory-CARCDHTN.js → tool-memory-PSQQ4NYD.js} +8 -8
- package/dist/{tool-memory-registry-adapters-QEC3YHB2.js → tool-memory-registry-adapters-UMPIJLHS.js} +3 -3
- package/dist/unified-registry-UEDZXMH3.js +26 -0
- package/dist/{weather-report-ZA7DBOC3.js → weather-report-CKEPMK5R.js} +4 -4
- package/package.json +1 -1
- package/dist/chunk-AH74OHVO.js +0 -1942
- package/dist/chunk-AH74OHVO.js.map +0 -1
- package/dist/chunk-NUBSJGQZ.js +0 -14
- package/dist/chunk-NUBSJGQZ.js.map +0 -1
- package/dist/chunk-OQLFRJCW.js.map +0 -1
- package/dist/chunk-PWSW5Z4L.js.map +0 -1
- package/dist/chunk-TEWDRLCM.js.map +0 -1
- package/dist/chunk-WPLXWTUK.js.map +0 -1
- package/dist/chunk-XOKWIL6I.js +0 -2455
- package/dist/chunk-XOKWIL6I.js.map +0 -1
- package/dist/doctor-deep-MC2FGSAF.js +0 -13
- package/dist/factory-G57M6CD4.js +0 -15
- package/dist/factory-T7KN4CKD.js +0 -25
- package/dist/issue-triage-KVYM5FQ5.js +0 -17
- package/dist/research-helpers-synthesize-M4SONOWL.js +0 -15
- package/dist/setup-command-LXMVWIKJ.js +0 -45
- package/dist/setup-config-PJNXP57G.js +0 -11
- package/dist/unified-registry-HVALHHPY.js +0 -26
- /package/dist/{child-mcp-config-OULJI7VH.js.map → child-mcp-config-FNVCS5MQ.js.map} +0 -0
- /package/dist/{chunk-4WVGF3SL.js.map → chunk-2RSNO5SP.js.map} +0 -0
- /package/dist/{chunk-B6C3JCQB.js.map → chunk-6JEPV4XW.js.map} +0 -0
- /package/dist/{chunk-VPTDFOAK.js.map → chunk-BLJY4YKP.js.map} +0 -0
- /package/dist/{chunk-YHY5AZL2.js.map → chunk-CHRJKE7S.js.map} +0 -0
- /package/dist/{chunk-LUBCO6ZN.js.map → chunk-CPRPZPX7.js.map} +0 -0
- /package/dist/{chunk-56LJJOBN.js.map → chunk-ED47MBY5.js.map} +0 -0
- /package/dist/{chunk-XWIFBBBH.js.map → chunk-EEJFMRHJ.js.map} +0 -0
- /package/dist/{chunk-NMNTYAV2.js.map → chunk-FFMDYBGA.js.map} +0 -0
- /package/dist/{chunk-V253VL7S.js.map → chunk-GEOALLHA.js.map} +0 -0
- /package/dist/{chunk-F6SRPGDK.js.map → chunk-GFCCNGI7.js.map} +0 -0
- /package/dist/{chunk-P45XNRHB.js.map → chunk-GT5B35X3.js.map} +0 -0
- /package/dist/{chunk-ARTQO7XZ.js.map → chunk-H7LRUN27.js.map} +0 -0
- /package/dist/{chunk-2HUC6NZN.js.map → chunk-HHLCAWAV.js.map} +0 -0
- /package/dist/{chunk-HBF7O6CN.js.map → chunk-HJK5RZBX.js.map} +0 -0
- /package/dist/{chunk-J6JEW4QZ.js.map → chunk-JD4HTIMG.js.map} +0 -0
- /package/dist/{chunk-6454KSCM.js.map → chunk-JPA3GAWT.js.map} +0 -0
- /package/dist/{chunk-XIIUIBSX.js.map → chunk-KGFFEQTN.js.map} +0 -0
- /package/dist/{chunk-WATNLQS7.js.map → chunk-L6QLJ4VK.js.map} +0 -0
- /package/dist/{chunk-232RDSCM.js.map → chunk-LWR2K2VM.js.map} +0 -0
- /package/dist/{chunk-F6MUN523.js.map → chunk-MU7J7D5Q.js.map} +0 -0
- /package/dist/{chunk-VENIFAMX.js.map → chunk-NOROX25U.js.map} +0 -0
- /package/dist/{chunk-7STKQ3IH.js.map → chunk-NX5VK5NW.js.map} +0 -0
- /package/dist/{chunk-SNJNRDKZ.js.map → chunk-RQV5ZUXT.js.map} +0 -0
- /package/dist/{chunk-KUGBMQAP.js.map → chunk-SQUEE5ZA.js.map} +0 -0
- /package/dist/{chunk-W6IZVEIM.js.map → chunk-T5WQAYX2.js.map} +0 -0
- /package/dist/{chunk-VZVGT4MN.js.map → chunk-UZRVS7MK.js.map} +0 -0
- /package/dist/{chunk-TANMACVQ.js.map → chunk-VFGP57F5.js.map} +0 -0
- /package/dist/{chunk-I6JMAM4J.js.map → chunk-VLDYF7EQ.js.map} +0 -0
- /package/dist/{chunk-5PCTGUMF.js.map → chunk-WHOODKBB.js.map} +0 -0
- /package/dist/{chunk-XGCYJLTP.js.map → chunk-XT4J7QAO.js.map} +0 -0
- /package/dist/{cli-circuit-breaker-4AVWA333.js.map → cli-circuit-breaker-A7GM4LXS.js.map} +0 -0
- /package/dist/{composite-router-7OTZN7NS.js.map → composite-router-SVZA4OCQ.js.map} +0 -0
- /package/dist/{consensus-vote-KFLDRUJJ.js.map → consensus-vote-7HGSTZKF.js.map} +0 -0
- /package/dist/{context-retriever-2NZWXF6Z.js.map → context-retriever-QTASR42L.js.map} +0 -0
- /package/dist/{doctor-deep-MC2FGSAF.js.map → doctor-deep-NBDOSO4E.js.map} +0 -0
- /package/dist/{doctor-live-25LRXKBO.js.map → doctor-live-HZWIMIT6.js.map} +0 -0
- /package/dist/{expert-bridge-2YLK3E6Q.js.map → expert-bridge-GY7RL4DF.js.map} +0 -0
- /package/dist/{factory-G57M6CD4.js.map → factory-4FZ5FPZW.js.map} +0 -0
- /package/dist/{factory-T7KN4CKD.js.map → factory-73NHOTVL.js.map} +0 -0
- /package/dist/{improvement-review-FFADRM4I.js.map → improvement-review-ZF3VHZEG.js.map} +0 -0
- /package/dist/{init-opencode-OSZF74AL.js.map → init-opencode-EOCW7P4L.js.map} +0 -0
- /package/dist/{issue-triage-KVYM5FQ5.js.map → issue-triage-L6UDFHF4.js.map} +0 -0
- /package/dist/{learning-persistence-EGY4VKKO.js.map → learning-persistence-SR5F4DSY.js.map} +0 -0
- /package/dist/{pr-reviewer-helpers-E3QXOVMT.js.map → pr-reviewer-helpers-KOE3X72Y.js.map} +0 -0
- /package/dist/{registry-command-ZMKZ5CRZ.js.map → registry-command-7ZENMDK4.js.map} +0 -0
- /package/dist/{repo-security-plan-BPET6KTP.js.map → repo-security-plan-PCHVMGHX.js.map} +0 -0
- /package/dist/{research-helpers-synthesize-M4SONOWL.js.map → research-helpers-synthesize-KTVQVLII.js.map} +0 -0
- /package/dist/{routing-memory-3UWP6NOX.js.map → routing-memory-QTBLYGMC.js.map} +0 -0
- /package/dist/{session-memory-N2ABITF7.js.map → session-memory-S7L4K5R5.js.map} +0 -0
- /package/dist/{setup-command-LXMVWIKJ.js.map → setup-config-7SWEJXN4.js.map} +0 -0
- /package/dist/{setup-custom-api-PE2M5HWL.js.map → setup-custom-api-WYXYQMQF.js.map} +0 -0
- /package/dist/{setup-config-PJNXP57G.js.map → setup-data-dir-EMXRXEFQ.js.map} +0 -0
- /package/dist/{tool-memory-CARCDHTN.js.map → tool-memory-PSQQ4NYD.js.map} +0 -0
- /package/dist/{tool-memory-registry-adapters-QEC3YHB2.js.map → tool-memory-registry-adapters-UMPIJLHS.js.map} +0 -0
- /package/dist/{unified-registry-HVALHHPY.js.map → unified-registry-UEDZXMH3.js.map} +0 -0
- /package/dist/{weather-report-ZA7DBOC3.js.map → weather-report-CKEPMK5R.js.map} +0 -0
package/dist/chunk-XOKWIL6I.js
DELETED
|
@@ -1,2455 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
readOpenAICompatEnv
|
|
3
|
-
} from "./chunk-VZVGT4MN.js";
|
|
4
|
-
import {
|
|
5
|
-
allOf
|
|
6
|
-
} from "./chunk-OQB3XC5O.js";
|
|
7
|
-
import {
|
|
8
|
-
resolveInsideRoot
|
|
9
|
-
} from "./chunk-NUBSJGQZ.js";
|
|
10
|
-
import {
|
|
11
|
-
capitalize
|
|
12
|
-
} from "./chunk-6T3EPABN.js";
|
|
13
|
-
import {
|
|
14
|
-
createAllAdapters,
|
|
15
|
-
probeCli
|
|
16
|
-
} from "./chunk-F6SRPGDK.js";
|
|
17
|
-
import {
|
|
18
|
-
DEFAULTS
|
|
19
|
-
} from "./chunk-B6C3JCQB.js";
|
|
20
|
-
import {
|
|
21
|
-
getNexusTmpDir
|
|
22
|
-
} from "./chunk-SNJNRDKZ.js";
|
|
23
|
-
import {
|
|
24
|
-
CliNameSchema,
|
|
25
|
-
DEFAULT_CAPABILITIES,
|
|
26
|
-
DEFAULT_COMPOSITE_CONFIG,
|
|
27
|
-
DEFAULT_ZERO_ROUTER_CONFIG,
|
|
28
|
-
TaskCategorySchema,
|
|
29
|
-
colors,
|
|
30
|
-
createLogger,
|
|
31
|
-
err,
|
|
32
|
-
formatZodIssuesAsArray,
|
|
33
|
-
getErrorMessage,
|
|
34
|
-
getInTreeCapabilitiesMatrix,
|
|
35
|
-
getTimeProvider,
|
|
36
|
-
ok,
|
|
37
|
-
symbols,
|
|
38
|
-
writeLine
|
|
39
|
-
} from "./chunk-4WVGF3SL.js";
|
|
40
|
-
import {
|
|
41
|
-
getLearningDir,
|
|
42
|
-
getOutcomesFile,
|
|
43
|
-
getRulesFile,
|
|
44
|
-
isPersistenceEnabled
|
|
45
|
-
} from "./chunk-2HUC6NZN.js";
|
|
46
|
-
import {
|
|
47
|
-
detectSandbox,
|
|
48
|
-
getNexusDataDir,
|
|
49
|
-
getNexusRepoDir,
|
|
50
|
-
getPerRepoSubdirs,
|
|
51
|
-
nexusDataPath
|
|
52
|
-
} from "./chunk-OQLFRJCW.js";
|
|
53
|
-
|
|
54
|
-
// src/version.ts
|
|
55
|
-
var VERSION = true ? "4.14.0" : "dev";
|
|
56
|
-
|
|
57
|
-
// src/config/schemas-core.ts
|
|
58
|
-
import { z } from "zod";
|
|
59
|
-
var LoggingConfigSchema = z.object({
|
|
60
|
-
level: z.enum(["debug", "info", "warn", "error"]).default("info"),
|
|
61
|
-
format: z.enum(["json", "pretty"]).default("json"),
|
|
62
|
-
// Default to stderr: MCP stdio transport reserves stdout for JSON-RPC frames.
|
|
63
|
-
destination: z.enum(["stdout", "stderr", "file"]).default("stderr"),
|
|
64
|
-
filePath: z.string().optional()
|
|
65
|
-
});
|
|
66
|
-
var ProviderConfigSchema = z.object({
|
|
67
|
-
apiKey: z.string().optional(),
|
|
68
|
-
baseUrl: z.url().optional(),
|
|
69
|
-
timeout: z.number().positive().default(3e4),
|
|
70
|
-
maxRetries: z.number().nonnegative().default(3)
|
|
71
|
-
});
|
|
72
|
-
var ModelTiersSchema = z.object({
|
|
73
|
-
fast: z.array(z.string()).min(1),
|
|
74
|
-
balanced: z.array(z.string()).min(1),
|
|
75
|
-
powerful: z.array(z.string()).min(1)
|
|
76
|
-
});
|
|
77
|
-
var ModelConfigSchema = z.object({
|
|
78
|
-
default: z.string(),
|
|
79
|
-
tiers: ModelTiersSchema,
|
|
80
|
-
providers: z.record(z.string(), ProviderConfigSchema).optional()
|
|
81
|
-
});
|
|
82
|
-
var WorkflowConfigSchema = z.object({
|
|
83
|
-
templatesDir: z.string().default("./workflows"),
|
|
84
|
-
timeout: z.number().positive().default(3e5),
|
|
85
|
-
maxParallel: z.number().positive().default(5)
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// src/config/schemas-expert.ts
|
|
89
|
-
import { z as z2 } from "zod";
|
|
90
|
-
var VALID_EXPERT_TIERS = ["fast", "balanced", "powerful"];
|
|
91
|
-
var VALID_EXPERT_DOMAINS = [
|
|
92
|
-
"code",
|
|
93
|
-
"security",
|
|
94
|
-
"architecture",
|
|
95
|
-
"documentation",
|
|
96
|
-
"testing",
|
|
97
|
-
"general"
|
|
98
|
-
];
|
|
99
|
-
var MAX_SYSTEM_PROMPT_LENGTH = 4e3;
|
|
100
|
-
var CustomExpertDefinitionSchema = z2.object({
|
|
101
|
-
/** System prompt defining the expert's persona (max 4000 characters) */
|
|
102
|
-
systemPrompt: z2.string().min(1, "System prompt is required").max(
|
|
103
|
-
MAX_SYSTEM_PROMPT_LENGTH,
|
|
104
|
-
`System prompt must be at most ${String(MAX_SYSTEM_PROMPT_LENGTH)} characters`
|
|
105
|
-
),
|
|
106
|
-
/** Model tier for routing */
|
|
107
|
-
tier: z2.enum(VALID_EXPERT_TIERS, {
|
|
108
|
-
error: `Invalid tier. Valid options: ${VALID_EXPERT_TIERS.join(", ")}`
|
|
109
|
-
}).default("balanced"),
|
|
110
|
-
/** Primary domain of expertise */
|
|
111
|
-
domain: z2.enum(VALID_EXPERT_DOMAINS, {
|
|
112
|
-
error: `Invalid domain. Valid options: ${VALID_EXPERT_DOMAINS.join(", ")}`
|
|
113
|
-
}).default("general"),
|
|
114
|
-
/** Secondary domains (optional) */
|
|
115
|
-
secondaryDomains: z2.array(z2.enum(VALID_EXPERT_DOMAINS)).optional(),
|
|
116
|
-
/** Expert capabilities */
|
|
117
|
-
capabilities: z2.array(z2.string().min(1)).min(1, "At least one capability is required").default(["task_execution"]),
|
|
118
|
-
/** Model temperature (0-1) */
|
|
119
|
-
temperature: z2.number().min(0).max(1).default(0.3),
|
|
120
|
-
/** Allowed tools (optional, unrestricted if not specified) */
|
|
121
|
-
tools: z2.array(z2.string()).optional(),
|
|
122
|
-
/** Human-readable description */
|
|
123
|
-
description: z2.string().optional(),
|
|
124
|
-
/** Weight for expert selection scoring (0-1) */
|
|
125
|
-
weight: z2.number().min(0).max(1).default(1),
|
|
126
|
-
/** Whether this expert is currently available */
|
|
127
|
-
available: z2.boolean().default(true)
|
|
128
|
-
});
|
|
129
|
-
var ExpertDefinitionSchema = z2.object({
|
|
130
|
-
prompt: z2.string().min(1),
|
|
131
|
-
tier: z2.enum(["fast", "balanced", "powerful"]).default("balanced"),
|
|
132
|
-
temperature: z2.number().min(0).max(1).default(0.3),
|
|
133
|
-
tools: z2.array(z2.string()).optional()
|
|
134
|
-
});
|
|
135
|
-
var ExpertConfigSchema = z2.object({
|
|
136
|
-
/** Enable built-in experts */
|
|
137
|
-
builtin: z2.boolean().default(true),
|
|
138
|
-
/** Custom expert definitions keyed by expert ID */
|
|
139
|
-
custom: z2.record(
|
|
140
|
-
z2.string().regex(/^[a-z][a-z0-9_]*$/, {
|
|
141
|
-
message: "Expert ID must start with a letter and contain only lowercase letters, numbers, and underscores"
|
|
142
|
-
}),
|
|
143
|
-
CustomExpertDefinitionSchema
|
|
144
|
-
).optional()
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
// src/config/schemas-security.ts
|
|
148
|
-
import { z as z3 } from "zod";
|
|
149
|
-
var PolicyConfigSchema = z3.object({
|
|
150
|
-
/** Default execution mode for tool operations (default: 'read-only') */
|
|
151
|
-
defaultMode: z3.enum(["read-only", "read-write"]).default("read-only"),
|
|
152
|
-
/** Policy enforcement mode (default: 'enforce') */
|
|
153
|
-
policyMode: z3.enum(["enforce", "warn"]).default("enforce")
|
|
154
|
-
});
|
|
155
|
-
var SandboxConfigSchema = z3.object({
|
|
156
|
-
/** Sandbox execution mode (default: 'policy') */
|
|
157
|
-
mode: z3.enum(["none", "policy", "container"]).default("policy"),
|
|
158
|
-
/** Fall back to policy mode if container mode unavailable (default: true) */
|
|
159
|
-
fallbackToPolicy: z3.boolean().default(true),
|
|
160
|
-
/** Docker image to use in container mode (default: 'node:22-alpine') */
|
|
161
|
-
dockerImage: z3.string().optional(),
|
|
162
|
-
/** Enable network access in container mode (default: false) */
|
|
163
|
-
networkEnabled: z3.boolean().default(false)
|
|
164
|
-
});
|
|
165
|
-
var TimeoutConfigSchema = z3.object({
|
|
166
|
-
/** Default timeout in milliseconds (default: 30000) */
|
|
167
|
-
defaultTimeoutMs: z3.number().positive().default(3e4),
|
|
168
|
-
/** Maximum timeout in milliseconds (default: 300000) */
|
|
169
|
-
maxTimeoutMs: z3.number().positive().default(3e5),
|
|
170
|
-
/** Whether to log timeout events (default: true) */
|
|
171
|
-
enableLogging: z3.boolean().default(true),
|
|
172
|
-
/** Enable URI validation to prevent ReDoS (default: true) */
|
|
173
|
-
uriValidation: z3.boolean().default(true),
|
|
174
|
-
/** Per-tool timeout overrides in milliseconds (Issue #657) */
|
|
175
|
-
perToolTimeout: z3.record(z3.string(), z3.number().positive().max(6e5)).optional()
|
|
176
|
-
});
|
|
177
|
-
var ToolRateLimitSchema = z3.object({
|
|
178
|
-
/** Maximum tokens (burst capacity) */
|
|
179
|
-
capacity: z3.number().positive().default(10),
|
|
180
|
-
/** Tokens refilled per interval */
|
|
181
|
-
refillRate: z3.number().positive().default(10),
|
|
182
|
-
/** Refill interval in milliseconds (default: 60000 = 1 minute) */
|
|
183
|
-
refillIntervalMs: z3.number().positive().default(6e4)
|
|
184
|
-
});
|
|
185
|
-
var DEFAULT_TOOL_RATE_LIMITS = {
|
|
186
|
-
orchestrate: { capacity: 10, refillRate: 10, refillIntervalMs: 6e4 },
|
|
187
|
-
delegate: { capacity: 20, refillRate: 20, refillIntervalMs: 6e4 },
|
|
188
|
-
workflow: { capacity: 5, refillRate: 5, refillIntervalMs: 6e4 },
|
|
189
|
-
expert: { capacity: 30, refillRate: 30, refillIntervalMs: 6e4 }
|
|
190
|
-
};
|
|
191
|
-
var SecurityConfigSchema = z3.object({
|
|
192
|
-
allowedPaths: z3.array(z3.string()).default(["./"]),
|
|
193
|
-
blockedPatterns: z3.array(z3.string()).default([]),
|
|
194
|
-
rateLimit: z3.object({
|
|
195
|
-
enabled: z3.boolean().default(true),
|
|
196
|
-
requestsPerMinute: z3.number().positive().default(60),
|
|
197
|
-
/** Per-tool rate limits (Issue #274 Phase 2) */
|
|
198
|
-
perTool: z3.record(z3.string(), ToolRateLimitSchema).optional()
|
|
199
|
-
}).default(() => ({ enabled: true, requestsPerMinute: 60 })),
|
|
200
|
-
secretsFile: z3.string().optional(),
|
|
201
|
-
/** Policy firewall configuration */
|
|
202
|
-
policy: PolicyConfigSchema.optional(),
|
|
203
|
-
/** Sandbox execution configuration (Issue #175) */
|
|
204
|
-
sandbox: SandboxConfigSchema.optional(),
|
|
205
|
-
/** Timeout configuration (Issue #271, CVE-2026-0621) */
|
|
206
|
-
timeout: TimeoutConfigSchema.optional(),
|
|
207
|
-
/** Tool allowlist — when set, only listed tools are registered (Issue #740) */
|
|
208
|
-
toolAllowlist: z3.array(z3.string()).optional(),
|
|
209
|
-
/** Audit logging configuration (Issue #740 Phase 2) */
|
|
210
|
-
audit: z3.object({
|
|
211
|
-
/** Enable audit logging (default: true) */
|
|
212
|
-
enabled: z3.boolean().default(true),
|
|
213
|
-
/** Log directory (default: ~/.nexus-agents/audit) */
|
|
214
|
-
logDir: z3.string().optional(),
|
|
215
|
-
/** Minimum severity to log (default: 'info') */
|
|
216
|
-
minSeverity: z3.enum(["info", "warning", "critical"]).default("info"),
|
|
217
|
-
/** Enable tamper-evident hash chain (default: true) */
|
|
218
|
-
enableHashChain: z3.boolean().default(true),
|
|
219
|
-
/** Maximum log file size in bytes (default: 10MB) */
|
|
220
|
-
maxFileSizeBytes: z3.number().positive().default(10 * 1024 * 1024),
|
|
221
|
-
/** Maximum number of log files to retain (default: 10) */
|
|
222
|
-
maxFiles: z3.number().positive().default(10)
|
|
223
|
-
}).optional(),
|
|
224
|
-
/** Authentication configuration (Issue #739) */
|
|
225
|
-
auth: z3.object({
|
|
226
|
-
/** Enable authentication for network-exposed transports (default: true) */
|
|
227
|
-
enabled: z3.boolean().default(true),
|
|
228
|
-
/** Authentication method (default: 'token') */
|
|
229
|
-
method: z3.enum(["token", "oauth2"]).default("token"),
|
|
230
|
-
/** Header name for bearer token (default: 'Authorization') */
|
|
231
|
-
tokenHeader: z3.string().default("Authorization"),
|
|
232
|
-
/** Token file path (default: ~/.nexus-agents/auth/server-token) */
|
|
233
|
-
tokenFile: z3.string().optional()
|
|
234
|
-
}).optional()
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
// src/config/schemas.ts
|
|
238
|
-
import { z as z9 } from "zod";
|
|
239
|
-
|
|
240
|
-
// src/config/schemas-observability.ts
|
|
241
|
-
import { z as z4 } from "zod";
|
|
242
|
-
var EventBusConfigSchema = z4.object({
|
|
243
|
-
/** Enable EventBus integration (default: true) */
|
|
244
|
-
enabled: z4.boolean().default(true),
|
|
245
|
-
/** Maximum events to retain in history (default: 1000) */
|
|
246
|
-
maxHistorySize: z4.number().positive().default(1e3),
|
|
247
|
-
/** Event patterns to subscribe to (default: all major patterns) */
|
|
248
|
-
subscriptions: z4.object({
|
|
249
|
-
/** Subscribe to consensus events (consensus.*) */
|
|
250
|
-
consensus: z4.boolean().default(true),
|
|
251
|
-
/** Subscribe to agent events (agent.*) */
|
|
252
|
-
agent: z4.boolean().default(true),
|
|
253
|
-
/** Subscribe to protocol events (protocol.*) */
|
|
254
|
-
protocol: z4.boolean().default(true),
|
|
255
|
-
/** Subscribe to session events (session.*) */
|
|
256
|
-
session: z4.boolean().default(true),
|
|
257
|
-
/** Subscribe to message events (message.*) */
|
|
258
|
-
message: z4.boolean().default(false),
|
|
259
|
-
// Off by default (high volume)
|
|
260
|
-
/** Subscribe to byzantine detection events (byzantine.*) */
|
|
261
|
-
byzantine: z4.boolean().default(true)
|
|
262
|
-
}).default(() => ({
|
|
263
|
-
consensus: true,
|
|
264
|
-
agent: true,
|
|
265
|
-
protocol: true,
|
|
266
|
-
session: true,
|
|
267
|
-
message: false,
|
|
268
|
-
byzantine: true
|
|
269
|
-
})),
|
|
270
|
-
/** Logging configuration for events */
|
|
271
|
-
logging: z4.object({
|
|
272
|
-
/** Log level for frequent events (default: debug) */
|
|
273
|
-
frequentEventLevel: z4.enum(["debug", "info"]).default("debug"),
|
|
274
|
-
/** Log level for important events (default: info) */
|
|
275
|
-
importantEventLevel: z4.enum(["debug", "info"]).default("info")
|
|
276
|
-
}).default(() => ({
|
|
277
|
-
frequentEventLevel: "debug",
|
|
278
|
-
importantEventLevel: "info"
|
|
279
|
-
}))
|
|
280
|
-
});
|
|
281
|
-
var ObservabilityConfigSchema = z4.object({
|
|
282
|
-
/** EventBus configuration */
|
|
283
|
-
eventBus: EventBusConfigSchema.optional(),
|
|
284
|
-
/** SwarmObserver maximum events (default: 10000) */
|
|
285
|
-
swarmObserverMaxEvents: z4.number().positive().default(1e4)
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
// src/config/schemas-skills.ts
|
|
289
|
-
import { z as z5 } from "zod";
|
|
290
|
-
var ExternalPackSourceSchema = z5.object({
|
|
291
|
-
/** Pack name for identification */
|
|
292
|
-
name: z5.string().min(1),
|
|
293
|
-
/** Source: npm package name or local file path */
|
|
294
|
-
source: z5.string().min(1),
|
|
295
|
-
/** Whether the pack is enabled (default: true) */
|
|
296
|
-
enabled: z5.boolean().default(true)
|
|
297
|
-
});
|
|
298
|
-
var SkillLibraryConfigSchema = z5.object({
|
|
299
|
-
/** Whether skill library is enabled (default: true) */
|
|
300
|
-
enabled: z5.boolean().default(true),
|
|
301
|
-
/** Maximum skills to store (default: 1000) */
|
|
302
|
-
maxSkills: z5.number().int().positive().default(1e3),
|
|
303
|
-
/** Minimum success rate to keep skill (0-1, default: 0.3) */
|
|
304
|
-
minSuccessRateForRetention: z5.number().min(0).max(1).default(0.3),
|
|
305
|
-
/** Number of executions before evaluating retention (default: 5) */
|
|
306
|
-
executionsBeforeEvaluation: z5.number().int().nonnegative().default(5),
|
|
307
|
-
/** Enable automatic skill pruning (default: true) */
|
|
308
|
-
enablePruning: z5.boolean().default(true),
|
|
309
|
-
/** Whether to track detailed execution history (default: true) */
|
|
310
|
-
trackExecutionHistory: z5.boolean().default(true),
|
|
311
|
-
/** Maximum execution history entries per skill (default: 100) */
|
|
312
|
-
maxHistoryPerSkill: z5.number().int().positive().default(100),
|
|
313
|
-
/** External skill pack sources (Issue #654) */
|
|
314
|
-
externalPacks: z5.array(ExternalPackSourceSchema).optional()
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
// src/config/schemas-sica.ts
|
|
318
|
-
import { z as z6 } from "zod";
|
|
319
|
-
var SicaConfigSchema = z6.object({
|
|
320
|
-
/** Whether SICA wrapping is enabled (default: false) */
|
|
321
|
-
enabled: z6.boolean().default(false),
|
|
322
|
-
/** Minimum executions before considering improvement (default: 10) */
|
|
323
|
-
minExecutionsForImprovement: z6.number().int().positive().default(10),
|
|
324
|
-
/** Success rate threshold to trigger improvement (0-1, default: 0.7) */
|
|
325
|
-
improvementThreshold: z6.number().min(0).max(1).default(0.7),
|
|
326
|
-
/** Maximum concurrent versions to evaluate (default: 3) */
|
|
327
|
-
maxActiveVersions: z6.number().int().positive().max(10).default(3),
|
|
328
|
-
/** Whether to auto-select best version (default: true) */
|
|
329
|
-
autoSelectBest: z6.boolean().default(true),
|
|
330
|
-
/** Improvement cooldown in milliseconds (default: 60000) */
|
|
331
|
-
improvementCooldownMs: z6.number().int().nonnegative().default(6e4),
|
|
332
|
-
/** Enable observability logging for SICA events (default: true) */
|
|
333
|
-
enableObservability: z6.boolean().default(true)
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
// src/config/schemas-gateway.ts
|
|
337
|
-
import { z as z7 } from "zod";
|
|
338
|
-
var TierNameSchema = z7.enum(["DIRECT", "ANALYZED", "ORCHESTRATED"]);
|
|
339
|
-
var ALLOWED_COMMANDS = ["node", "npx", "python", "python3", "uvx", "docker"];
|
|
340
|
-
var UpstreamServerSchema = z7.object({
|
|
341
|
-
/** Unique name for this upstream server (used as tool prefix). */
|
|
342
|
-
name: z7.string().min(1).max(50).regex(/^[a-z0-9_-]+$/),
|
|
343
|
-
/** Command to spawn the server. Must be in allowlist. */
|
|
344
|
-
command: z7.enum(ALLOWED_COMMANDS),
|
|
345
|
-
/** Arguments to pass to the command. */
|
|
346
|
-
args: z7.array(z7.string().max(500)).default([]),
|
|
347
|
-
/** Environment variables (use {env:VAR} references, not plaintext secrets). */
|
|
348
|
-
env: z7.record(z7.string(), z7.string()).optional(),
|
|
349
|
-
/** Whether to connect lazily on first tool call (default: true). */
|
|
350
|
-
lazy: z7.boolean().default(true),
|
|
351
|
-
/** Connection timeout in ms (default: 10000). */
|
|
352
|
-
timeoutMs: z7.number().int().min(1e3).max(6e4).default(1e4)
|
|
353
|
-
});
|
|
354
|
-
var MAX_UPSTREAM_SERVERS = 5;
|
|
355
|
-
var GatewayConfigSchema = z7.object({
|
|
356
|
-
/** Enable gateway tier dispatch logging (default: true). */
|
|
357
|
-
enabled: z7.boolean().default(true),
|
|
358
|
-
/**
|
|
359
|
-
* Per-tool tier overrides.
|
|
360
|
-
* Keys are tool names (e.g., 'delegate_to_model'), values are tier names.
|
|
361
|
-
* Overrides the default tier from TOOL_TIER_MAP in tier-classifier.ts.
|
|
362
|
-
*/
|
|
363
|
-
tierOverrides: z7.record(z7.string(), TierNameSchema).optional(),
|
|
364
|
-
/**
|
|
365
|
-
* Upstream MCP servers to compose with (#1498).
|
|
366
|
-
* Tools from upstream servers are available as prefixed tools (e.g., tavily.search).
|
|
367
|
-
*/
|
|
368
|
-
upstreamServers: z7.array(UpstreamServerSchema).max(MAX_UPSTREAM_SERVERS).optional()
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
// src/config/schemas-routing.ts
|
|
372
|
-
import { z as z8 } from "zod";
|
|
373
|
-
var BudgetConstraintsSchema = z8.object({
|
|
374
|
-
/** Maximum tokens per request */
|
|
375
|
-
maxTokens: z8.number().positive().optional(),
|
|
376
|
-
/** Maximum cost per request in USD */
|
|
377
|
-
maxCostUsd: z8.number().positive().optional(),
|
|
378
|
-
/** Maximum latency per request in milliseconds */
|
|
379
|
-
maxLatencyMs: z8.number().positive().optional(),
|
|
380
|
-
/**
|
|
381
|
-
* Per-task-class cost ceilings in USD, keyed by `TaskCategory`
|
|
382
|
-
* (#4196, #4214). Keys are validated against the enum so a typo'd
|
|
383
|
-
* class fails config parsing instead of silently configuring nothing.
|
|
384
|
-
* Absent → no ceiling (OFF/unlimited). Enforced only under
|
|
385
|
-
* `NEXUS_BILLING_MODE=api`; a candidate with missing registry pricing
|
|
386
|
-
* fails a configured ceiling (fail-closed).
|
|
387
|
-
*/
|
|
388
|
-
taskClassMaxCostUsd: z8.partialRecord(TaskCategorySchema, z8.number().positive()).optional()
|
|
389
|
-
}).optional();
|
|
390
|
-
var TopsisCriterionSchema = z8.object({
|
|
391
|
-
/** Criterion name (e.g., 'quality', 'cost', 'latency') */
|
|
392
|
-
name: z8.string().min(1),
|
|
393
|
-
/** Weight for this criterion (0-1, should sum to 1 across all criteria) */
|
|
394
|
-
weight: z8.number().min(0).max(1),
|
|
395
|
-
/** Whether higher values are better (true) or lower is better (false) */
|
|
396
|
-
beneficial: z8.boolean()
|
|
397
|
-
});
|
|
398
|
-
var TopsisConfigSchema = z8.object({
|
|
399
|
-
/** Criteria with weights (must sum to 1.0) */
|
|
400
|
-
criteria: z8.array(TopsisCriterionSchema).optional(),
|
|
401
|
-
/** Minimum acceptable quality score (0-10) */
|
|
402
|
-
minQualityThreshold: z8.number().min(0).max(10).default(5),
|
|
403
|
-
/** Maximum acceptable latency in milliseconds (optional) */
|
|
404
|
-
maxLatencyMs: z8.number().positive().optional(),
|
|
405
|
-
/** Maximum acceptable cost per request in USD (optional) */
|
|
406
|
-
maxCostPerRequest: z8.number().positive().optional(),
|
|
407
|
-
/** Whether to log detailed scoring info */
|
|
408
|
-
verbose: z8.boolean().default(false)
|
|
409
|
-
}).optional();
|
|
410
|
-
var DifficultyWeightsConfigSchema = z8.object({
|
|
411
|
-
/** Weight for reasoning difficulty (0-1) */
|
|
412
|
-
reasoning: z8.number().min(0).max(1).default(0.3),
|
|
413
|
-
/** Weight for knowledge difficulty (0-1) */
|
|
414
|
-
knowledge: z8.number().min(0).max(1).default(0.15),
|
|
415
|
-
/** Weight for creativity difficulty (0-1) */
|
|
416
|
-
creativity: z8.number().min(0).max(1).default(0.15),
|
|
417
|
-
/** Weight for precision difficulty (0-1) */
|
|
418
|
-
precision: z8.number().min(0).max(1).default(0.25),
|
|
419
|
-
/** Weight for context length difficulty (0-1) */
|
|
420
|
-
context_length: z8.number().min(0).max(1).default(0.15)
|
|
421
|
-
});
|
|
422
|
-
var DifficultyThresholdsSchema = z8.object({
|
|
423
|
-
/** Upper bound for 'easy' classification (0-1) */
|
|
424
|
-
easyUpperBound: z8.number().min(0).max(1).default(0.3),
|
|
425
|
-
/** Lower bound for 'hard' classification (0-1) */
|
|
426
|
-
hardLowerBound: z8.number().min(0).max(1).default(0.7)
|
|
427
|
-
});
|
|
428
|
-
var DifficultyLevelSchema = z8.enum(["easy", "medium", "hard"]);
|
|
429
|
-
var ModelTierSchema = z8.enum(["fast", "balanced", "powerful"]);
|
|
430
|
-
var ZeroRouterConfigSchema = z8.object({
|
|
431
|
-
/** Difficulty thresholds for level classification */
|
|
432
|
-
thresholds: DifficultyThresholdsSchema.optional(),
|
|
433
|
-
/** Weights for difficulty aggregation */
|
|
434
|
-
weights: DifficultyWeightsConfigSchema.optional(),
|
|
435
|
-
/** Mapping from difficulty level to model tier */
|
|
436
|
-
difficultyToTier: z8.record(DifficultyLevelSchema, ModelTierSchema).optional(),
|
|
437
|
-
/** Mapping from model tier to CLI preference order */
|
|
438
|
-
tierToClis: z8.record(ModelTierSchema, z8.array(CliNameSchema)).optional(),
|
|
439
|
-
/** Enable adaptive calibration from outcomes */
|
|
440
|
-
enableCalibration: z8.boolean().default(true),
|
|
441
|
-
/** Maximum outcomes to store for calibration */
|
|
442
|
-
maxCalibrationOutcomes: z8.number().int().positive().default(1e3),
|
|
443
|
-
/** Minimum outcomes before applying calibration adjustments */
|
|
444
|
-
minCalibrationOutcomes: z8.number().int().positive().default(50),
|
|
445
|
-
/** Verbose logging */
|
|
446
|
-
verbose: z8.boolean().default(false)
|
|
447
|
-
}).optional();
|
|
448
|
-
var LatencyTrackerConfigSchema = z8.object({
|
|
449
|
-
/** Maximum number of samples to keep per CLI */
|
|
450
|
-
windowSize: z8.number().int().positive().default(100),
|
|
451
|
-
/** Time-weighted decay factor (0-1, higher = more weight to recent) */
|
|
452
|
-
decayFactor: z8.number().min(0).max(1).default(0.95),
|
|
453
|
-
/** Maximum age of samples in milliseconds before forced eviction */
|
|
454
|
-
maxSampleAgeMs: z8.number().int().positive().default(36e5),
|
|
455
|
-
/** Percentiles to calculate */
|
|
456
|
-
percentiles: z8.array(z8.number().min(0).max(100)).max(20).default([50, 95, 99])
|
|
457
|
-
}).optional();
|
|
458
|
-
var RoutingMemoryConfigSchema = z8.object({
|
|
459
|
-
/** Minimum observations before using learned routing */
|
|
460
|
-
minObservations: z8.number().int().positive().default(5),
|
|
461
|
-
/** Confidence threshold for using cached decisions */
|
|
462
|
-
confidenceThreshold: z8.number().min(0).max(1).default(0.6),
|
|
463
|
-
/** Success rate threshold for trusting a routing pattern */
|
|
464
|
-
successRateThreshold: z8.number().min(0).max(1).default(0.7),
|
|
465
|
-
/** Maximum age of cached actions in milliseconds */
|
|
466
|
-
actionCacheMaxAgeMs: z8.number().int().positive().default(36e5)
|
|
467
|
-
}).optional();
|
|
468
|
-
var RoutingConfigSchema = z8.object({
|
|
469
|
-
/**
|
|
470
|
-
* Pipeline stage toggles.
|
|
471
|
-
* Enable or disable individual routing stages.
|
|
472
|
-
*/
|
|
473
|
-
stages: z8.object({
|
|
474
|
-
/** Enable budget filtering stage */
|
|
475
|
-
budgetFilter: z8.boolean().default(true),
|
|
476
|
-
/** Enable ZeroRouter difficulty-based routing */
|
|
477
|
-
zeroRouter: z8.boolean().default(true),
|
|
478
|
-
/** Enable preference-trained routing */
|
|
479
|
-
preferenceRouting: z8.boolean().default(false),
|
|
480
|
-
/** Enable TOPSIS ranking */
|
|
481
|
-
topsisRanking: z8.boolean().default(true),
|
|
482
|
-
/** Enable LinUCB selection */
|
|
483
|
-
linucbSelection: z8.boolean().default(true),
|
|
484
|
-
/** Enable latency tracking */
|
|
485
|
-
latencyTracking: z8.boolean().default(true),
|
|
486
|
-
/** Enable routing memory for learned routing */
|
|
487
|
-
routingMemory: z8.boolean().default(false),
|
|
488
|
-
/** Enable confidence cascade stage (Issue #755, ADR-0005) */
|
|
489
|
-
confidenceCascade: z8.boolean().default(false),
|
|
490
|
-
/** Enable capability match stage (Issue #755, ADR-0005) */
|
|
491
|
-
capabilityMatch: z8.boolean().default(false),
|
|
492
|
-
/** Enable quality constraint stage (Issue #755, ADR-0005) */
|
|
493
|
-
qualityConstraint: z8.boolean().default(false),
|
|
494
|
-
/** Enable resource strategy stage for budget-aware oscillation (Issue #998) */
|
|
495
|
-
resourceStrategy: z8.boolean().default(true),
|
|
496
|
-
/** Enable strategy distillation for learned routing rules (Issue #999) */
|
|
497
|
-
strategyDistillation: z8.boolean().default(false)
|
|
498
|
-
}).optional(),
|
|
499
|
-
/**
|
|
500
|
-
* Budget constraints for routing decisions.
|
|
501
|
-
*/
|
|
502
|
-
budget: BudgetConstraintsSchema,
|
|
503
|
-
/**
|
|
504
|
-
* TOPSIS multi-criteria decision making configuration.
|
|
505
|
-
*/
|
|
506
|
-
topsis: TopsisConfigSchema,
|
|
507
|
-
/**
|
|
508
|
-
* ZeroRouter difficulty-based routing configuration.
|
|
509
|
-
*/
|
|
510
|
-
zeroRouter: ZeroRouterConfigSchema,
|
|
511
|
-
/**
|
|
512
|
-
* Latency tracker configuration.
|
|
513
|
-
*/
|
|
514
|
-
latencyTracker: LatencyTrackerConfigSchema,
|
|
515
|
-
/**
|
|
516
|
-
* Routing memory configuration.
|
|
517
|
-
*/
|
|
518
|
-
routingMemory: RoutingMemoryConfigSchema,
|
|
519
|
-
/**
|
|
520
|
-
* LinUCB bandit parameters.
|
|
521
|
-
*/
|
|
522
|
-
linucb: z8.object({
|
|
523
|
-
/** Exploration parameter (higher = more exploration) */
|
|
524
|
-
alpha: z8.number().positive().default(1),
|
|
525
|
-
/** Maximum routing decision time in milliseconds */
|
|
526
|
-
maxDecisionTimeMs: z8.number().positive().default(50)
|
|
527
|
-
}).optional(),
|
|
528
|
-
/**
|
|
529
|
-
* Preference router parameters.
|
|
530
|
-
*/
|
|
531
|
-
preference: z8.object({
|
|
532
|
-
/** Minimum data points before using learned preferences */
|
|
533
|
-
minDataPoints: z8.number().int().positive().default(10)
|
|
534
|
-
}).optional(),
|
|
535
|
-
/**
|
|
536
|
-
* Weight for latency score in final routing (0-1).
|
|
537
|
-
*/
|
|
538
|
-
latencyScoreWeight: z8.number().min(0).max(1).default(0.2)
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
// src/config/schemas.ts
|
|
542
|
-
var AppConfigSchema = z9.object({
|
|
543
|
-
models: ModelConfigSchema,
|
|
544
|
-
experts: ExpertConfigSchema.optional(),
|
|
545
|
-
workflows: WorkflowConfigSchema.optional(),
|
|
546
|
-
security: SecurityConfigSchema.optional(),
|
|
547
|
-
logging: LoggingConfigSchema.optional(),
|
|
548
|
-
/** Observability configuration (Issue #307) */
|
|
549
|
-
observability: ObservabilityConfigSchema.optional(),
|
|
550
|
-
/** Routing configuration (Issue #475) - used in orchestrator mode */
|
|
551
|
-
routing: RoutingConfigSchema.optional(),
|
|
552
|
-
/** Skill library configuration (Issue #491) */
|
|
553
|
-
skills: SkillLibraryConfigSchema.optional(),
|
|
554
|
-
/** SICA self-improvement configuration (Issue #492) */
|
|
555
|
-
sica: SicaConfigSchema.optional(),
|
|
556
|
-
/** Gateway middleware configuration (Issue #897) */
|
|
557
|
-
gateway: GatewayConfigSchema.optional()
|
|
558
|
-
});
|
|
559
|
-
var defaultConfig = {
|
|
560
|
-
models: {
|
|
561
|
-
default: "claude-sonnet",
|
|
562
|
-
tiers: {
|
|
563
|
-
fast: ["claude-haiku", "gemini-flash"],
|
|
564
|
-
balanced: ["claude-sonnet", "gemini-pro"],
|
|
565
|
-
powerful: ["claude-opus", "codex-5.3"]
|
|
566
|
-
}
|
|
567
|
-
},
|
|
568
|
-
logging: {
|
|
569
|
-
level: "info",
|
|
570
|
-
format: "json",
|
|
571
|
-
// stderr is the safe default: MCP stdio transport reserves stdout for
|
|
572
|
-
// JSON-RPC frames. Any log written to stdout corrupts the transport.
|
|
573
|
-
destination: "stderr"
|
|
574
|
-
},
|
|
575
|
-
security: {
|
|
576
|
-
allowedPaths: ["./"],
|
|
577
|
-
blockedPatterns: [],
|
|
578
|
-
rateLimit: {
|
|
579
|
-
enabled: true,
|
|
580
|
-
requestsPerMinute: 60
|
|
581
|
-
},
|
|
582
|
-
policy: {
|
|
583
|
-
defaultMode: "read-only",
|
|
584
|
-
policyMode: "enforce"
|
|
585
|
-
},
|
|
586
|
-
timeout: {
|
|
587
|
-
defaultTimeoutMs: 3e4,
|
|
588
|
-
maxTimeoutMs: 3e5,
|
|
589
|
-
enableLogging: true,
|
|
590
|
-
uriValidation: true
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
};
|
|
594
|
-
|
|
595
|
-
// src/config/env-schema.ts
|
|
596
|
-
import { z as z10 } from "zod";
|
|
597
|
-
|
|
598
|
-
// src/string-distance.ts
|
|
599
|
-
function at(arr, i) {
|
|
600
|
-
return arr[i] ?? 0;
|
|
601
|
-
}
|
|
602
|
-
function levenshtein(a, b) {
|
|
603
|
-
const m = a.length;
|
|
604
|
-
const n = b.length;
|
|
605
|
-
let prev = Array.from({ length: n + 1 }, (_, j) => j);
|
|
606
|
-
let curr = new Array(n + 1).fill(0);
|
|
607
|
-
for (let i = 1; i <= m; i++) {
|
|
608
|
-
curr[0] = i;
|
|
609
|
-
for (let j = 1; j <= n; j++) {
|
|
610
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
611
|
-
curr[j] = Math.min(at(prev, j) + 1, at(curr, j - 1) + 1, at(prev, j - 1) + cost);
|
|
612
|
-
}
|
|
613
|
-
[prev, curr] = [curr, prev];
|
|
614
|
-
}
|
|
615
|
-
return at(prev, n);
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
// src/config/env-schema.ts
|
|
619
|
-
var positiveIntStr = z10.string().regex(/^\d+$/, "Must be a positive integer string");
|
|
620
|
-
var boolStr = z10.enum(["true", "false"]);
|
|
621
|
-
var floatStr = z10.string().regex(/^\d+(\.\d+)?$/, "Must be a non-negative number string");
|
|
622
|
-
var NexusEnvSchema = z10.object({
|
|
623
|
-
// --- Timeouts ---
|
|
624
|
-
NEXUS_TIMEOUT_CLI: positiveIntStr.optional(),
|
|
625
|
-
// NEXUS_TIMEOUT_CLISIMPLE + NEXUS_TIMEOUT_CLICOMPLEX removed in #4180 — silent
|
|
626
|
-
// no-ops (#2977 class). Their only possible reader, getTimeout('cliSimpleMs' /
|
|
627
|
-
// 'cliComplexMs'), had zero production call sites; per-complexity CLI timeouts
|
|
628
|
-
// flow through getTimeoutForCli/TIMEOUT_PROFILES instead.
|
|
629
|
-
NEXUS_TIMEOUT_API: positiveIntStr.optional(),
|
|
630
|
-
NEXUS_TIMEOUT_WORKFLOW: positiveIntStr.optional(),
|
|
631
|
-
NEXUS_TIMEOUT_MCP: positiveIntStr.optional(),
|
|
632
|
-
NEXUS_VOTE_TIMEOUT_MS: positiveIntStr.optional(),
|
|
633
|
-
NEXUS_MCP_TIMEOUT_MS: positiveIntStr.optional(),
|
|
634
|
-
NEXUS_WORKFLOW_TIMEOUT_MS: positiveIntStr.optional(),
|
|
635
|
-
NEXUS_GRAPH_TIMEOUT_MS: positiveIntStr.optional(),
|
|
636
|
-
// NEXUS_TEST_TIMEOUT_MS removed in #4180 — silent no-op (#2977 class). No
|
|
637
|
-
// production reader ever constructed this name; TEST_TIMEOUTS takes no env
|
|
638
|
-
// override. Re-register only alongside a real consumer.
|
|
639
|
-
NEXUS_EXPERT_TIMEOUT_MS: positiveIntStr.optional(),
|
|
640
|
-
NEXUS_WORKER_TIMEOUT_MS: positiveIntStr.optional(),
|
|
641
|
-
// --- Central timeout authority (#3734) ---
|
|
642
|
-
// Global scale applied to every operation-class runaway-guard (clamped 0.25–10).
|
|
643
|
-
NEXUS_TIMEOUT_MULTIPLIER: floatStr.optional(),
|
|
644
|
-
// Per-operation-class guard overrides (ms). One per OperationClassName; env-schema
|
|
645
|
-
// can't match dynamic names, so the six are registered explicitly.
|
|
646
|
-
NEXUS_TIMEOUT_CLASS_INTERACTIVE_MS: positiveIntStr.optional(),
|
|
647
|
-
NEXUS_TIMEOUT_CLASS_SINGLE_LLM_MS: positiveIntStr.optional(),
|
|
648
|
-
NEXUS_TIMEOUT_CLASS_MULTI_LLM_PANEL_MS: positiveIntStr.optional(),
|
|
649
|
-
NEXUS_TIMEOUT_CLASS_PIPELINE_MS: positiveIntStr.optional(),
|
|
650
|
-
NEXUS_TIMEOUT_CLASS_NETWORK_FETCH_MS: positiveIntStr.optional(),
|
|
651
|
-
NEXUS_TIMEOUT_CLASS_ASYNC_JOB_BODY_MS: positiveIntStr.optional(),
|
|
652
|
-
// --- Retry ---
|
|
653
|
-
NEXUS_RETRY_MAX_RETRIES: positiveIntStr.optional(),
|
|
654
|
-
NEXUS_RETRY_BASE_DELAY: positiveIntStr.optional(),
|
|
655
|
-
NEXUS_RETRY_MAX_DELAY: positiveIntStr.optional(),
|
|
656
|
-
NEXUS_RETRY_JITTER: floatStr.optional(),
|
|
657
|
-
// --- Rate Limit ---
|
|
658
|
-
NEXUS_RATE_LIMIT_ENABLED: boolStr.optional(),
|
|
659
|
-
NEXUS_RATE_LIMIT_RPM: positiveIntStr.optional(),
|
|
660
|
-
NEXUS_RATE_LIMIT_MAX_CONCURRENT: positiveIntStr.optional(),
|
|
661
|
-
NEXUS_RATE_LIMIT_CAPACITY: positiveIntStr.optional(),
|
|
662
|
-
NEXUS_RATE_LIMIT_REFILL_RATE: positiveIntStr.optional(),
|
|
663
|
-
NEXUS_RATE_LIMIT_REFILL_INTERVAL: positiveIntStr.optional(),
|
|
664
|
-
// --- Workers & Concurrency ---
|
|
665
|
-
NEXUS_MAX_CONCURRENT_EXPERTS: positiveIntStr.optional(),
|
|
666
|
-
// NEXUS_WORKERS_* + NEXUS_WORKFLOW_MAX_PARALLEL + NEXUS_TEST_PARALLELISM +
|
|
667
|
-
// NEXUS_EVALUATION_MAX_WORKERS + NEXUS_SWARM_OBSERVER_MAX_EVENTS removed in
|
|
668
|
-
// #2977 — these had zero production consumers (silent no-ops).
|
|
669
|
-
// --- Circuit Breaker ---
|
|
670
|
-
NEXUS_CIRCUIT_BREAKER_THRESHOLD: positiveIntStr.optional(),
|
|
671
|
-
NEXUS_CIRCUIT_BREAKER_RESET_TIMEOUT: positiveIntStr.optional(),
|
|
672
|
-
// --- V2 Pipeline ---
|
|
673
|
-
NEXUS_V2_MODE: z10.enum(["off", "partial", "full"]).optional(),
|
|
674
|
-
NEXUS_V2_DELEGATE: boolStr.optional(),
|
|
675
|
-
NEXUS_V2_ORCHESTRATE: boolStr.optional(),
|
|
676
|
-
NEXUS_V2_POLICY_MODE: z10.enum(["off", "warn", "block"]).optional(),
|
|
677
|
-
NEXUS_AORCHESTRA: boolStr.optional(),
|
|
678
|
-
NEXUS_AORCHESTRA_DISPATCH: boolStr.optional(),
|
|
679
|
-
NEXUS_WORKER_MAX_CALLS: positiveIntStr.optional(),
|
|
680
|
-
// --- Server ---
|
|
681
|
-
NEXUS_AUTH_ENABLED: boolStr.optional(),
|
|
682
|
-
NEXUS_AUTH_METHOD: z10.string().optional(),
|
|
683
|
-
// --- Logging ---
|
|
684
|
-
NEXUS_LOG_LEVEL: z10.enum(["trace", "debug", "info", "warn", "error", "fatal", "silent"]).optional(),
|
|
685
|
-
// --- Features ---
|
|
686
|
-
NEXUS_PERSIST_LEARNING: boolStr.optional(),
|
|
687
|
-
NEXUS_REFLECTIVE_MEMORY: z10.enum(["true", "false", "shadow"]).optional(),
|
|
688
|
-
NEXUS_EVENTBUS_ENABLED: boolStr.optional(),
|
|
689
|
-
// NEXUS_EVENTBUS_MAX_HISTORY removed in #2977 — silent no-op (no production reader).
|
|
690
|
-
NEXUS_BILLING_MODE: z10.enum(["plan", "api"]).optional(),
|
|
691
|
-
NEXUS_CONFIG_PATH: z10.string().optional(),
|
|
692
|
-
// Scratch root for short-lived working files (#4412, getNexusTmpDir). Unset
|
|
693
|
-
// resolves to `<dataDir>/tmp`; set it to relocate scratch off the repo.
|
|
694
|
-
NEXUS_TMPDIR: z10.string().optional(),
|
|
695
|
-
NEXUS_ALLOW_MOCK_ORCHESTRATION: boolStr.optional(),
|
|
696
|
-
// Explicit opt-in for simulateVotes outside test runners (#4170) — read by
|
|
697
|
-
// checkSimulationAllowed (mcp/tools/simulation-guard.ts). Unset = fail closed.
|
|
698
|
-
NEXUS_ALLOW_SIMULATE: z10.enum(["0", "1"]).optional(),
|
|
699
|
-
// --- Autonomous remediation & policy (#3540 / #3653) ---
|
|
700
|
-
// Drives the auto-remediation cycle (resolveAutoRemediateMode); default audit
|
|
701
|
-
// (zero-write soak, #3769) when unset, explicit `off` disables.
|
|
702
|
-
NEXUS_AUTO_REMEDIATE: z10.enum(["off", "audit", "enforce"]).optional(),
|
|
703
|
-
// Stage-boundary policy gate enforcement mode (getGateEnforcementMode); warn by default.
|
|
704
|
-
NEXUS_POLICY_GATE_MODE: z10.enum(["off", "warn", "block"]).optional(),
|
|
705
|
-
// Path to a model-registry overlay manifest (buildDefaultRegistry / #3185 hot-reload).
|
|
706
|
-
NEXUS_MODELS_OVERLAY_PATH: z10.string().optional(),
|
|
707
|
-
// Render the unified cross-ranked memory prefix instead of per-backend sections (#3236); off by default.
|
|
708
|
-
NEXUS_CONTEXT_RANKED: z10.enum(["0", "1"]).optional(),
|
|
709
|
-
// Attach a ranked, budgeted repo-map (module import graph, PageRank-centrality)
|
|
710
|
-
// to context for structural tasks (#4254, getRepoMapForTask); pull-shaped +
|
|
711
|
-
// rank-gated, off by default.
|
|
712
|
-
NEXUS_REPO_MAP: z10.enum(["0", "1"]).optional(),
|
|
713
|
-
// Allow LLM-based pipeline classification when keyword scoring finds no
|
|
714
|
-
// evidence (#4677). Off by default: fixing the confidence floor made the
|
|
715
|
-
// enrichment gate reachable for the first time, and measurement put that at
|
|
716
|
-
// ~60% of realistic goals — one LLM call each. Opt in deliberately.
|
|
717
|
-
NEXUS_LLM_CLASSIFICATION: z10.enum(["0", "1"]).optional(),
|
|
718
|
-
// Feed live dispatch outcomes into the MetaOrchestrator shadow selector + persist them (#3593); off by default.
|
|
719
|
-
NEXUS_META_SHADOW_TRAIN: z10.enum(["0", "1"]).optional(),
|
|
720
|
-
// Resolve a concrete model from the difficulty tier at route time (#3394,
|
|
721
|
-
// isRouteModelSelectionEnabled); off by default. Registered here in #4197 —
|
|
722
|
-
// the reader predates this schema entry.
|
|
723
|
-
NEXUS_ROUTE_MODEL_SELECTION: boolStr.optional(),
|
|
724
|
-
// Record would-be tier model selections (shadow) + join them with outcomes for
|
|
725
|
-
// the offline flip eval (#4197, isRouteModelShadowEnabled); off by default.
|
|
726
|
-
NEXUS_ROUTE_MODEL_SHADOW: z10.enum(["0", "1"]).optional(),
|
|
727
|
-
// Async job-result reader source (#3090/#3693): `task_state` prefers/unions the
|
|
728
|
-
// Stage-2 task-state log; default (unset) is sidecar-only. Reader half of the
|
|
729
|
-
// sidecar→Stage-2 migration (epic #2631).
|
|
730
|
-
NEXUS_JOB_RESULT_SOURCE: z10.enum(["sidecar", "task_state"]).optional(),
|
|
731
|
-
// --- Hooks & Sessions ---
|
|
732
|
-
NEXUS_HOOK_VERBOSE: boolStr.optional(),
|
|
733
|
-
NEXUS_SESSIONS_DB: z10.string().optional(),
|
|
734
|
-
NEXUS_DISABLE_SESSIONS: boolStr.optional(),
|
|
735
|
-
NEXUS_DISABLE_METRICS: boolStr.optional()
|
|
736
|
-
});
|
|
737
|
-
var KNOWN_NAMES = Object.keys(NexusEnvSchema.shape);
|
|
738
|
-
function suggestSimilar(name, known) {
|
|
739
|
-
let best = null;
|
|
740
|
-
let bestDist = 4;
|
|
741
|
-
for (const candidate of known) {
|
|
742
|
-
const dist = levenshtein(name, candidate);
|
|
743
|
-
if (dist < bestDist) {
|
|
744
|
-
bestDist = dist;
|
|
745
|
-
best = candidate;
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
return best;
|
|
749
|
-
}
|
|
750
|
-
function classifyEnvKeys(nexusKeys, env) {
|
|
751
|
-
const knownRecord = {};
|
|
752
|
-
const unknownVars = [];
|
|
753
|
-
for (const key of nexusKeys) {
|
|
754
|
-
if (KNOWN_NAMES.includes(key)) {
|
|
755
|
-
const value = env[key];
|
|
756
|
-
if (value !== void 0) {
|
|
757
|
-
knownRecord[key] = value;
|
|
758
|
-
}
|
|
759
|
-
} else {
|
|
760
|
-
unknownVars.push({
|
|
761
|
-
name: key,
|
|
762
|
-
suggestion: suggestSimilar(key, KNOWN_NAMES)
|
|
763
|
-
});
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
return { knownRecord, unknownVars };
|
|
767
|
-
}
|
|
768
|
-
function logValidationWarnings(logger2, unknownVars, invalidVars) {
|
|
769
|
-
for (const u of unknownVars) {
|
|
770
|
-
const hint = u.suggestion !== null ? ` (did you mean ${u.suggestion}?)` : "";
|
|
771
|
-
logger2.warn(`Unknown environment variable: ${u.name}${hint}`);
|
|
772
|
-
}
|
|
773
|
-
for (const inv of invalidVars) {
|
|
774
|
-
logger2.warn(`Invalid environment variable ${inv.name}="${inv.value}": ${inv.error}`);
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
function validateNexusEnv(logger2) {
|
|
778
|
-
const nexusKeys = Object.keys(process.env).filter((k) => k.startsWith("NEXUS_"));
|
|
779
|
-
const { knownRecord, unknownVars } = classifyEnvKeys(nexusKeys, process.env);
|
|
780
|
-
const invalidVars = [];
|
|
781
|
-
const result = NexusEnvSchema.safeParse(knownRecord);
|
|
782
|
-
if (!result.success) {
|
|
783
|
-
for (const issue of result.error.issues) {
|
|
784
|
-
const varName = String(issue.path[0]);
|
|
785
|
-
invalidVars.push({
|
|
786
|
-
name: varName,
|
|
787
|
-
value: knownRecord[varName] ?? "",
|
|
788
|
-
error: issue.message
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
if (logger2 !== void 0) {
|
|
793
|
-
logValidationWarnings(logger2, unknownVars, invalidVars);
|
|
794
|
-
}
|
|
795
|
-
return { unknownVars, invalidVars };
|
|
796
|
-
}
|
|
797
|
-
function getKnownNexusVarNames() {
|
|
798
|
-
return KNOWN_NAMES;
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
// src/config/routing-config-adapter.ts
|
|
802
|
-
var ADAPTER_DEFAULTS = {
|
|
803
|
-
latencyScoreWeight: 0.2
|
|
804
|
-
};
|
|
805
|
-
function adaptZeroRouterConfig(yaml2) {
|
|
806
|
-
if (yaml2 === void 0) return {};
|
|
807
|
-
return {
|
|
808
|
-
thresholds: yaml2.thresholds ?? DEFAULT_ZERO_ROUTER_CONFIG.thresholds,
|
|
809
|
-
weights: yaml2.weights ?? DEFAULT_ZERO_ROUTER_CONFIG.weights,
|
|
810
|
-
difficultyToTier: yaml2.difficultyToTier ?? DEFAULT_ZERO_ROUTER_CONFIG.difficultyToTier,
|
|
811
|
-
tierToClis: yaml2.tierToClis ?? DEFAULT_ZERO_ROUTER_CONFIG.tierToClis,
|
|
812
|
-
enableCalibration: yaml2.enableCalibration,
|
|
813
|
-
maxCalibrationOutcomes: yaml2.maxCalibrationOutcomes,
|
|
814
|
-
minCalibrationOutcomes: yaml2.minCalibrationOutcomes,
|
|
815
|
-
verbose: yaml2.verbose
|
|
816
|
-
};
|
|
817
|
-
}
|
|
818
|
-
function adaptLatencyTrackerConfig(yaml2) {
|
|
819
|
-
if (yaml2 === void 0) return {};
|
|
820
|
-
return {
|
|
821
|
-
windowSize: yaml2.windowSize,
|
|
822
|
-
decayFactor: yaml2.decayFactor,
|
|
823
|
-
maxSampleAgeMs: yaml2.maxSampleAgeMs,
|
|
824
|
-
percentiles: yaml2.percentiles
|
|
825
|
-
};
|
|
826
|
-
}
|
|
827
|
-
function adaptRoutingMemoryConfig(yaml2) {
|
|
828
|
-
if (yaml2 === void 0) return {};
|
|
829
|
-
return {
|
|
830
|
-
minObservations: yaml2.minObservations,
|
|
831
|
-
confidenceThreshold: yaml2.confidenceThreshold,
|
|
832
|
-
successRateThreshold: yaml2.successRateThreshold,
|
|
833
|
-
actionCacheMaxAgeMs: yaml2.actionCacheMaxAgeMs
|
|
834
|
-
};
|
|
835
|
-
}
|
|
836
|
-
function adaptStagesConfig(yaml2) {
|
|
837
|
-
if (yaml2 === void 0) return {};
|
|
838
|
-
return {
|
|
839
|
-
enableBudgetFilter: yaml2.budgetFilter,
|
|
840
|
-
enableZeroRouter: yaml2.zeroRouter,
|
|
841
|
-
enablePreferenceRouting: yaml2.preferenceRouting,
|
|
842
|
-
enableTopsisRanking: yaml2.topsisRanking,
|
|
843
|
-
enableLinUCBSelection: yaml2.linucbSelection,
|
|
844
|
-
enableLatencyTracking: yaml2.latencyTracking,
|
|
845
|
-
enableRoutingMemory: yaml2.routingMemory,
|
|
846
|
-
// Issue #755: New replacement stages
|
|
847
|
-
enableConfidenceCascade: yaml2.confidenceCascade,
|
|
848
|
-
enableCapabilityMatch: yaml2.capabilityMatch,
|
|
849
|
-
enableQualityConstraint: yaml2.qualityConstraint,
|
|
850
|
-
// Issue #998: Resource strategy
|
|
851
|
-
enableResourceStrategy: yaml2.resourceStrategy,
|
|
852
|
-
// Issue #999: Strategy distillation
|
|
853
|
-
enableStrategyDistillation: yaml2.strategyDistillation
|
|
854
|
-
};
|
|
855
|
-
}
|
|
856
|
-
var STAGE_FLAG_KEYS = [
|
|
857
|
-
"enableBudgetFilter",
|
|
858
|
-
"enableZeroRouter",
|
|
859
|
-
"enablePreferenceRouting",
|
|
860
|
-
"enableTopsisRanking",
|
|
861
|
-
"enableLinUCBSelection",
|
|
862
|
-
"enableLatencyTracking",
|
|
863
|
-
"enableRoutingMemory",
|
|
864
|
-
"enableConfidenceCascade",
|
|
865
|
-
"enableCapabilityMatch",
|
|
866
|
-
"enableQualityConstraint",
|
|
867
|
-
"enableResourceStrategy",
|
|
868
|
-
"enableStrategyDistillation",
|
|
869
|
-
"enableKnnRouting",
|
|
870
|
-
"enableCapacityBalancing"
|
|
871
|
-
];
|
|
872
|
-
var PERSISTENCE_AWARE_FLAGS = /* @__PURE__ */ new Set([
|
|
873
|
-
"enableRoutingMemory",
|
|
874
|
-
"enableStrategyDistillation",
|
|
875
|
-
"enablePreferenceRouting"
|
|
876
|
-
]);
|
|
877
|
-
function resolveStageFlags(stagesConfig) {
|
|
878
|
-
const persistenceOn = isPersistenceEnabled();
|
|
879
|
-
const result = {};
|
|
880
|
-
for (const key of STAGE_FLAG_KEYS) {
|
|
881
|
-
const explicit = stagesConfig[key];
|
|
882
|
-
if (explicit !== void 0) {
|
|
883
|
-
result[key] = explicit;
|
|
884
|
-
} else if (PERSISTENCE_AWARE_FLAGS.has(key) && persistenceOn) {
|
|
885
|
-
result[key] = true;
|
|
886
|
-
} else {
|
|
887
|
-
result[key] = DEFAULT_COMPOSITE_CONFIG[key];
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
return result;
|
|
891
|
-
}
|
|
892
|
-
function resolveBillingModeFromEnv() {
|
|
893
|
-
return process.env["NEXUS_BILLING_MODE"] === "api" ? "api" : DEFAULT_COMPOSITE_CONFIG.billingMode;
|
|
894
|
-
}
|
|
895
|
-
function buildBaseConfig(config, stagesConfig) {
|
|
896
|
-
const stageFlags = resolveStageFlags(stagesConfig);
|
|
897
|
-
return {
|
|
898
|
-
...stageFlags,
|
|
899
|
-
billingMode: resolveBillingModeFromEnv(),
|
|
900
|
-
latencyScoreWeight: config.latencyScoreWeight,
|
|
901
|
-
budgetConstraints: config.budget,
|
|
902
|
-
linucbAlpha: config.linucb?.alpha ?? DEFAULT_COMPOSITE_CONFIG.linucbAlpha,
|
|
903
|
-
maxDecisionTimeMs: config.linucb?.maxDecisionTimeMs ?? DEFAULT_COMPOSITE_CONFIG.maxDecisionTimeMs,
|
|
904
|
-
preferenceMinDataPoints: config.preference?.minDataPoints ?? DEFAULT_COMPOSITE_CONFIG.preferenceMinDataPoints
|
|
905
|
-
};
|
|
906
|
-
}
|
|
907
|
-
function adaptRoutingConfig(yamlConfig) {
|
|
908
|
-
const config = yamlConfig ?? ADAPTER_DEFAULTS;
|
|
909
|
-
const stagesConfig = adaptStagesConfig(config.stages);
|
|
910
|
-
const baseConfig = buildBaseConfig(config, stagesConfig);
|
|
911
|
-
return {
|
|
912
|
-
...baseConfig,
|
|
913
|
-
zeroRouterConfig: adaptZeroRouterConfig(config.zeroRouter),
|
|
914
|
-
latencyTrackerConfig: adaptLatencyTrackerConfig(config.latencyTracker),
|
|
915
|
-
routingMemoryConfig: adaptRoutingMemoryConfig(config.routingMemory)
|
|
916
|
-
};
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
// src/config/config-manager.ts
|
|
920
|
-
import { z as z11 } from "zod";
|
|
921
|
-
var NumericValueSchema = z11.number();
|
|
922
|
-
var BooleanValueSchema = z11.boolean();
|
|
923
|
-
var StringValueSchema = z11.string();
|
|
924
|
-
function validateValue(value, expectedType) {
|
|
925
|
-
if (typeof expectedType === "number") return NumericValueSchema.safeParse(value).success;
|
|
926
|
-
if (typeof expectedType === "boolean") return BooleanValueSchema.safeParse(value).success;
|
|
927
|
-
if (typeof expectedType === "string") return StringValueSchema.safeParse(value).success;
|
|
928
|
-
return false;
|
|
929
|
-
}
|
|
930
|
-
var ENV_VAR_MAP = {
|
|
931
|
-
"TIMEOUT_DEFAULTS.cliMs": "NEXUS_TIMEOUT_CLI",
|
|
932
|
-
"TIMEOUT_DEFAULTS.apiMs": "NEXUS_TIMEOUT_API",
|
|
933
|
-
"TIMEOUT_DEFAULTS.workflowMs": "NEXUS_TIMEOUT_WORKFLOW",
|
|
934
|
-
"TIMEOUT_DEFAULTS.mcpMs": "NEXUS_TIMEOUT_MCP",
|
|
935
|
-
"RATE_LIMIT_DEFAULTS.requestsPerMinute": "NEXUS_RATE_LIMIT_RPM",
|
|
936
|
-
"RATE_LIMIT_DEFAULTS.enabled": "NEXUS_RATE_LIMIT_ENABLED",
|
|
937
|
-
"RATE_LIMIT_DEFAULTS.maxConcurrent": "NEXUS_RATE_LIMIT_MAX_CONCURRENT",
|
|
938
|
-
"RATE_LIMIT_DEFAULTS.capacity": "NEXUS_RATE_LIMIT_CAPACITY",
|
|
939
|
-
"RATE_LIMIT_DEFAULTS.refillRate": "NEXUS_RATE_LIMIT_REFILL_RATE",
|
|
940
|
-
"RATE_LIMIT_DEFAULTS.refillIntervalMs": "NEXUS_RATE_LIMIT_REFILL_INTERVAL",
|
|
941
|
-
"RETRY_DEFAULTS.maxRetries": "NEXUS_RETRY_MAX_RETRIES",
|
|
942
|
-
"RETRY_DEFAULTS.baseDelayMs": "NEXUS_RETRY_BASE_DELAY",
|
|
943
|
-
"RETRY_DEFAULTS.maxDelayMs": "NEXUS_RETRY_MAX_DELAY",
|
|
944
|
-
"RETRY_DEFAULTS.jitterFactor": "NEXUS_RETRY_JITTER",
|
|
945
|
-
// WORKER_DEFAULTS.* mappings removed in #2977 — zero production consumers.
|
|
946
|
-
"CIRCUIT_BREAKER_DEFAULTS.failureThreshold": "NEXUS_CIRCUIT_BREAKER_THRESHOLD",
|
|
947
|
-
"CIRCUIT_BREAKER_DEFAULTS.resetTimeoutMs": "NEXUS_CIRCUIT_BREAKER_RESET_TIMEOUT"
|
|
948
|
-
};
|
|
949
|
-
function parseEnvValue(envValue, defaultValue) {
|
|
950
|
-
if (typeof defaultValue === "number") {
|
|
951
|
-
const parsed = Number(envValue);
|
|
952
|
-
return !isNaN(parsed) && isFinite(parsed) ? parsed : void 0;
|
|
953
|
-
}
|
|
954
|
-
if (typeof defaultValue === "boolean") {
|
|
955
|
-
const lower = envValue.toLowerCase();
|
|
956
|
-
if (lower === "true" || lower === "1") return true;
|
|
957
|
-
if (lower === "false" || lower === "0") return false;
|
|
958
|
-
return void 0;
|
|
959
|
-
}
|
|
960
|
-
if (typeof defaultValue === "string") return envValue;
|
|
961
|
-
return void 0;
|
|
962
|
-
}
|
|
963
|
-
var ConfigManager = class _ConfigManager {
|
|
964
|
-
static instance = null;
|
|
965
|
-
overrides = /* @__PURE__ */ new Map();
|
|
966
|
-
constructor() {
|
|
967
|
-
}
|
|
968
|
-
/** Gets the singleton instance. */
|
|
969
|
-
static getInstance() {
|
|
970
|
-
_ConfigManager.instance ??= new _ConfigManager();
|
|
971
|
-
return _ConfigManager.instance;
|
|
972
|
-
}
|
|
973
|
-
/** Resets the singleton instance (for testing). */
|
|
974
|
-
static resetInstance() {
|
|
975
|
-
_ConfigManager.instance = null;
|
|
976
|
-
}
|
|
977
|
-
/** Gets the effective value for a configuration key. */
|
|
978
|
-
get(category, key) {
|
|
979
|
-
return this.getWithMeta(category, key).value;
|
|
980
|
-
}
|
|
981
|
-
/** Gets the effective value with source metadata. */
|
|
982
|
-
getWithMeta(category, key) {
|
|
983
|
-
const fullKey = `${category}.${String(key)}`;
|
|
984
|
-
const defaultValue = DEFAULTS[category][key];
|
|
985
|
-
const override = this.overrides.get(fullKey);
|
|
986
|
-
if (override) {
|
|
987
|
-
return {
|
|
988
|
-
value: override.value,
|
|
989
|
-
source: override.source,
|
|
990
|
-
key: fullKey,
|
|
991
|
-
isOverride: true,
|
|
992
|
-
defaultValue
|
|
993
|
-
};
|
|
994
|
-
}
|
|
995
|
-
const envVar = ENV_VAR_MAP[fullKey];
|
|
996
|
-
if (envVar !== void 0 && envVar.length > 0) {
|
|
997
|
-
const envValue = process.env[envVar];
|
|
998
|
-
if (envValue !== void 0) {
|
|
999
|
-
const parsed = parseEnvValue(envValue, defaultValue);
|
|
1000
|
-
if (parsed !== void 0) {
|
|
1001
|
-
return { value: parsed, source: "env", key: fullKey, isOverride: true, defaultValue };
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
return {
|
|
1006
|
-
value: defaultValue,
|
|
1007
|
-
source: "package",
|
|
1008
|
-
key: fullKey,
|
|
1009
|
-
isOverride: false,
|
|
1010
|
-
defaultValue
|
|
1011
|
-
};
|
|
1012
|
-
}
|
|
1013
|
-
/**
|
|
1014
|
-
* Sets an override for a configuration key.
|
|
1015
|
-
* @throws {Error} If value fails type validation
|
|
1016
|
-
*/
|
|
1017
|
-
setOverride(category, key, value, source) {
|
|
1018
|
-
const fullKey = `${category}.${String(key)}`;
|
|
1019
|
-
const defaultValue = DEFAULTS[category][key];
|
|
1020
|
-
if (!validateValue(value, defaultValue)) {
|
|
1021
|
-
throw new Error(
|
|
1022
|
-
`Invalid value type for ${fullKey}: expected ${typeof defaultValue}, got ${typeof value}`
|
|
1023
|
-
);
|
|
1024
|
-
}
|
|
1025
|
-
this.overrides.set(fullKey, { value, source, setAt: new Date(getTimeProvider().now()) });
|
|
1026
|
-
}
|
|
1027
|
-
/** Clears an override. Returns true if it existed. */
|
|
1028
|
-
clearOverride(category, key) {
|
|
1029
|
-
return this.overrides.delete(`${category}.${String(key)}`);
|
|
1030
|
-
}
|
|
1031
|
-
/** Clears all overrides. */
|
|
1032
|
-
clearAllOverrides() {
|
|
1033
|
-
this.overrides.clear();
|
|
1034
|
-
}
|
|
1035
|
-
/** Lists all active overrides. */
|
|
1036
|
-
listOverrides() {
|
|
1037
|
-
return Array.from(this.overrides.entries()).map(([key, override]) => ({
|
|
1038
|
-
key,
|
|
1039
|
-
value: override.value,
|
|
1040
|
-
source: override.source,
|
|
1041
|
-
setAt: override.setAt
|
|
1042
|
-
}));
|
|
1043
|
-
}
|
|
1044
|
-
/** Lists all configuration keys with their effective values. */
|
|
1045
|
-
listAll(categoryFilter) {
|
|
1046
|
-
const categories = categoryFilter ? [categoryFilter] : Object.keys(DEFAULTS);
|
|
1047
|
-
const result = [];
|
|
1048
|
-
for (const category of categories) {
|
|
1049
|
-
for (const key of Object.keys(DEFAULTS[category])) {
|
|
1050
|
-
const fullKey = `${category}.${key}`;
|
|
1051
|
-
const meta = this.getWithMeta(category, key);
|
|
1052
|
-
result.push({
|
|
1053
|
-
category,
|
|
1054
|
-
key,
|
|
1055
|
-
value: meta.value,
|
|
1056
|
-
source: meta.source,
|
|
1057
|
-
envVar: ENV_VAR_MAP[fullKey]
|
|
1058
|
-
});
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
return result;
|
|
1062
|
-
}
|
|
1063
|
-
/** Gets the environment variable name for a config key. */
|
|
1064
|
-
getEnvVarName(category, key) {
|
|
1065
|
-
return ENV_VAR_MAP[`${category}.${String(key)}`];
|
|
1066
|
-
}
|
|
1067
|
-
/** Checks if a key has an active override. */
|
|
1068
|
-
hasOverride(category, key) {
|
|
1069
|
-
return this.overrides.has(`${category}.${String(key)}`);
|
|
1070
|
-
}
|
|
1071
|
-
};
|
|
1072
|
-
function getConfigManager() {
|
|
1073
|
-
return ConfigManager.getInstance();
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
// src/config/config-loader.ts
|
|
1077
|
-
import { readFileSync, existsSync } from "fs";
|
|
1078
|
-
import { resolve, join } from "path";
|
|
1079
|
-
import * as yaml from "yaml";
|
|
1080
|
-
var DEFAULT_CONFIG_FILE = "nexus-agents.yaml";
|
|
1081
|
-
var ALTERNATE_CONFIG_FILE = "nexus-agents.yml";
|
|
1082
|
-
var CONFIG_LOOKUP_PATHS = [
|
|
1083
|
-
`.nexus-agents/${DEFAULT_CONFIG_FILE}`,
|
|
1084
|
-
`.nexus-agents/${ALTERNATE_CONFIG_FILE}`,
|
|
1085
|
-
DEFAULT_CONFIG_FILE,
|
|
1086
|
-
ALTERNATE_CONFIG_FILE
|
|
1087
|
-
];
|
|
1088
|
-
var ConfigLoadError = class extends Error {
|
|
1089
|
-
code;
|
|
1090
|
-
constructor(message, code, cause) {
|
|
1091
|
-
super(message, { cause });
|
|
1092
|
-
this.name = "ConfigLoadError";
|
|
1093
|
-
this.code = code;
|
|
1094
|
-
}
|
|
1095
|
-
};
|
|
1096
|
-
function validatePath(userPath, root) {
|
|
1097
|
-
const resolved = resolveInsideRoot(userPath, root);
|
|
1098
|
-
if (resolved === null) {
|
|
1099
|
-
return err(
|
|
1100
|
-
new ConfigLoadError(
|
|
1101
|
-
`Config path traversal detected: ${userPath} escapes ${root}`,
|
|
1102
|
-
"PATH_TRAVERSAL"
|
|
1103
|
-
)
|
|
1104
|
-
);
|
|
1105
|
-
}
|
|
1106
|
-
return ok(resolved);
|
|
1107
|
-
}
|
|
1108
|
-
function findConfigPath(cwd) {
|
|
1109
|
-
const envPath = process.env["NEXUS_CONFIG_PATH"];
|
|
1110
|
-
if (envPath !== void 0 && envPath !== "") {
|
|
1111
|
-
const validation = validatePath(envPath, cwd);
|
|
1112
|
-
if (validation.ok && existsSync(validation.value)) {
|
|
1113
|
-
return validation.value;
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
for (const rel of CONFIG_LOOKUP_PATHS) {
|
|
1117
|
-
const abs = resolve(cwd, rel);
|
|
1118
|
-
if (existsSync(abs)) {
|
|
1119
|
-
return abs;
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
const globalDir = getNexusDataDir();
|
|
1123
|
-
const globalYamlPath = join(globalDir, DEFAULT_CONFIG_FILE);
|
|
1124
|
-
if (existsSync(globalYamlPath)) {
|
|
1125
|
-
return globalYamlPath;
|
|
1126
|
-
}
|
|
1127
|
-
const globalYmlPath = join(globalDir, ALTERNATE_CONFIG_FILE);
|
|
1128
|
-
if (existsSync(globalYmlPath)) {
|
|
1129
|
-
return globalYmlPath;
|
|
1130
|
-
}
|
|
1131
|
-
return void 0;
|
|
1132
|
-
}
|
|
1133
|
-
function parseYaml(content) {
|
|
1134
|
-
try {
|
|
1135
|
-
const parsed = yaml.parse(content);
|
|
1136
|
-
return ok(parsed);
|
|
1137
|
-
} catch (error) {
|
|
1138
|
-
const message = error instanceof Error ? error.message : "Unknown YAML parse error";
|
|
1139
|
-
return err(
|
|
1140
|
-
new ConfigLoadError(`YAML parse error: ${message}`, "YAML_PARSE_ERROR", error)
|
|
1141
|
-
);
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
var PROTOTYPE_POLLUTION_KEYS = /* @__PURE__ */ new Set([
|
|
1145
|
-
"__proto__",
|
|
1146
|
-
"constructor",
|
|
1147
|
-
"prototype"
|
|
1148
|
-
]);
|
|
1149
|
-
function isMergeableObject(value) {
|
|
1150
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1151
|
-
}
|
|
1152
|
-
function deepMerge(target, source) {
|
|
1153
|
-
const result = { ...target };
|
|
1154
|
-
for (const key of Object.keys(source)) {
|
|
1155
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
1156
|
-
const sourceValue = source[key];
|
|
1157
|
-
if (sourceValue === void 0) continue;
|
|
1158
|
-
const targetValue = target[key];
|
|
1159
|
-
if (isMergeableObject(sourceValue) && isMergeableObject(targetValue)) {
|
|
1160
|
-
result[key] = deepMerge(targetValue, sourceValue);
|
|
1161
|
-
} else {
|
|
1162
|
-
result[key] = sourceValue;
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
return result;
|
|
1166
|
-
}
|
|
1167
|
-
function loadDefaultConfig(logger2) {
|
|
1168
|
-
logger2.debug("No config file found, using defaults");
|
|
1169
|
-
const validated = AppConfigSchema.safeParse(defaultConfig);
|
|
1170
|
-
if (!validated.success) {
|
|
1171
|
-
return err(
|
|
1172
|
-
new ConfigLoadError(
|
|
1173
|
-
`Default config validation failed: ${validated.error.message}`,
|
|
1174
|
-
"VALIDATION_ERROR"
|
|
1175
|
-
)
|
|
1176
|
-
);
|
|
1177
|
-
}
|
|
1178
|
-
return ok({
|
|
1179
|
-
config: validated.data,
|
|
1180
|
-
usingDefaults: true,
|
|
1181
|
-
warnings: ["No config file found, using default configuration"]
|
|
1182
|
-
});
|
|
1183
|
-
}
|
|
1184
|
-
function readAndParseConfig(configPath, mergeDefaults, logger2) {
|
|
1185
|
-
let content;
|
|
1186
|
-
try {
|
|
1187
|
-
content = readFileSync(configPath, "utf-8");
|
|
1188
|
-
} catch (error) {
|
|
1189
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1190
|
-
return err(
|
|
1191
|
-
new ConfigLoadError(
|
|
1192
|
-
`Failed to read config file: ${message}`,
|
|
1193
|
-
"FILE_READ_ERROR",
|
|
1194
|
-
error
|
|
1195
|
-
)
|
|
1196
|
-
);
|
|
1197
|
-
}
|
|
1198
|
-
const parseResult = parseYaml(content);
|
|
1199
|
-
if (!parseResult.ok) return err(parseResult.error);
|
|
1200
|
-
if (mergeDefaults && typeof parseResult.value === "object" && parseResult.value !== null) {
|
|
1201
|
-
logger2.debug("Merged config with defaults");
|
|
1202
|
-
return ok(
|
|
1203
|
-
deepMerge(
|
|
1204
|
-
defaultConfig,
|
|
1205
|
-
parseResult.value
|
|
1206
|
-
)
|
|
1207
|
-
);
|
|
1208
|
-
}
|
|
1209
|
-
return ok(parseResult.value);
|
|
1210
|
-
}
|
|
1211
|
-
function validateConfig(configData, configPath, logger2) {
|
|
1212
|
-
const validation = AppConfigSchema.safeParse(configData);
|
|
1213
|
-
if (!validation.success) {
|
|
1214
|
-
const issues = formatZodIssuesAsArray(validation.error);
|
|
1215
|
-
return err(
|
|
1216
|
-
new ConfigLoadError(`Config validation failed:
|
|
1217
|
-
${issues.join("\n")}`, "VALIDATION_ERROR")
|
|
1218
|
-
);
|
|
1219
|
-
}
|
|
1220
|
-
logger2.info("Configuration loaded successfully", { configPath });
|
|
1221
|
-
return ok({ config: validation.data, configPath, usingDefaults: false, warnings: [] });
|
|
1222
|
-
}
|
|
1223
|
-
function loadConfig(options = {}) {
|
|
1224
|
-
const { configPath: explicitPath, cwd = process.cwd(), mergeDefaults = true } = options;
|
|
1225
|
-
const logger2 = options.logger ?? createLogger({ component: "ConfigLoader" });
|
|
1226
|
-
let configPath;
|
|
1227
|
-
if (explicitPath !== void 0) {
|
|
1228
|
-
const validation = validatePath(explicitPath, cwd);
|
|
1229
|
-
if (!validation.ok) return err(validation.error);
|
|
1230
|
-
configPath = validation.value;
|
|
1231
|
-
} else {
|
|
1232
|
-
configPath = findConfigPath(cwd);
|
|
1233
|
-
}
|
|
1234
|
-
if (configPath === void 0) return loadDefaultConfig(logger2);
|
|
1235
|
-
const parseResult = readAndParseConfig(configPath, mergeDefaults, logger2);
|
|
1236
|
-
if (!parseResult.ok) return err(parseResult.error);
|
|
1237
|
-
return validateConfig(parseResult.value, configPath, logger2);
|
|
1238
|
-
}
|
|
1239
|
-
var loadedConfig;
|
|
1240
|
-
function getConfig(options) {
|
|
1241
|
-
if (loadedConfig !== void 0) {
|
|
1242
|
-
return loadedConfig;
|
|
1243
|
-
}
|
|
1244
|
-
const result = loadConfig(options);
|
|
1245
|
-
if (!result.ok) {
|
|
1246
|
-
throw result.error;
|
|
1247
|
-
}
|
|
1248
|
-
loadedConfig = result.value;
|
|
1249
|
-
return loadedConfig;
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
// src/cli/setup-data-dir.ts
|
|
1253
|
-
import { mkdirSync, existsSync as existsSync4 } from "fs";
|
|
1254
|
-
|
|
1255
|
-
// src/cli/doctor.ts
|
|
1256
|
-
import {
|
|
1257
|
-
existsSync as existsSync3,
|
|
1258
|
-
readFileSync as readFileSync3,
|
|
1259
|
-
readdirSync,
|
|
1260
|
-
accessSync,
|
|
1261
|
-
constants as fsConstants
|
|
1262
|
-
} from "fs";
|
|
1263
|
-
|
|
1264
|
-
// src/cli/doctor-scratch-space.ts
|
|
1265
|
-
import { statSync, statfsSync } from "fs";
|
|
1266
|
-
import { tmpdir } from "os";
|
|
1267
|
-
var MIB = 1024 * 1024;
|
|
1268
|
-
var GIB = 1024 * 1024 * 1024;
|
|
1269
|
-
var CRITICAL_FREE_BYTES = 512 * MIB;
|
|
1270
|
-
var WARN_FREE_BYTES = 2 * GIB;
|
|
1271
|
-
function formatBytes(bytes) {
|
|
1272
|
-
if (bytes >= GIB) return `${(bytes / GIB).toFixed(1)} GiB`;
|
|
1273
|
-
return `${(bytes / MIB).toFixed(0)} MiB`;
|
|
1274
|
-
}
|
|
1275
|
-
function grade(freeBytes) {
|
|
1276
|
-
if (freeBytes < CRITICAL_FREE_BYTES) return "critical";
|
|
1277
|
-
if (freeBytes < WARN_FREE_BYTES) return "warn";
|
|
1278
|
-
return "ok";
|
|
1279
|
-
}
|
|
1280
|
-
function checkScratchSpace(root = getNexusTmpDir(), statfs = statfsSync, label = "nexus") {
|
|
1281
|
-
let reading;
|
|
1282
|
-
try {
|
|
1283
|
-
reading = statfs(root);
|
|
1284
|
-
} catch {
|
|
1285
|
-
return {
|
|
1286
|
-
label,
|
|
1287
|
-
root,
|
|
1288
|
-
available: false,
|
|
1289
|
-
freeBytes: 0,
|
|
1290
|
-
totalBytes: 0,
|
|
1291
|
-
percentUsed: 0,
|
|
1292
|
-
severity: "ok",
|
|
1293
|
-
message: `Scratch filesystem at ${root} could not be read (statfs unavailable)`
|
|
1294
|
-
};
|
|
1295
|
-
}
|
|
1296
|
-
const totalBytes = reading.blocks * reading.bsize;
|
|
1297
|
-
const freeBytes = reading.bavail * reading.bsize;
|
|
1298
|
-
const percentUsed = totalBytes > 0 ? Math.round((totalBytes - freeBytes) / totalBytes * 100) : 100;
|
|
1299
|
-
const severity = grade(freeBytes);
|
|
1300
|
-
return {
|
|
1301
|
-
label,
|
|
1302
|
-
root,
|
|
1303
|
-
available: true,
|
|
1304
|
-
freeBytes,
|
|
1305
|
-
totalBytes,
|
|
1306
|
-
percentUsed,
|
|
1307
|
-
severity,
|
|
1308
|
-
message: `${formatBytes(freeBytes)} free of ${formatBytes(totalBytes)} (${String(percentUsed)}% used)`
|
|
1309
|
-
};
|
|
1310
|
-
}
|
|
1311
|
-
var SEVERITY_ORDER = ["ok", "warn", "critical"];
|
|
1312
|
-
function defaultRoots() {
|
|
1313
|
-
return [
|
|
1314
|
-
{ label: "nexus", root: getNexusTmpDir() },
|
|
1315
|
-
{ label: "system", root: tmpdir() }
|
|
1316
|
-
];
|
|
1317
|
-
}
|
|
1318
|
-
function checkScratchFilesystems(options = {}) {
|
|
1319
|
-
const roots = options.roots ?? defaultRoots();
|
|
1320
|
-
const statfs = options.statfs ?? statfsSync;
|
|
1321
|
-
const deviceOf = options.deviceOf ?? ((path) => statSync(path).dev);
|
|
1322
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1323
|
-
const checks = [];
|
|
1324
|
-
for (const { label, root } of roots) {
|
|
1325
|
-
let device;
|
|
1326
|
-
try {
|
|
1327
|
-
device = deviceOf(root);
|
|
1328
|
-
} catch {
|
|
1329
|
-
continue;
|
|
1330
|
-
}
|
|
1331
|
-
if (seen.has(device)) continue;
|
|
1332
|
-
seen.add(device);
|
|
1333
|
-
checks.push(checkScratchSpace(root, statfs, label));
|
|
1334
|
-
}
|
|
1335
|
-
return checks;
|
|
1336
|
-
}
|
|
1337
|
-
function worstSeverity(checks) {
|
|
1338
|
-
return checks.reduce(
|
|
1339
|
-
(worst, check) => SEVERITY_ORDER.indexOf(check.severity) > SEVERITY_ORDER.indexOf(worst) ? check.severity : worst,
|
|
1340
|
-
"ok"
|
|
1341
|
-
);
|
|
1342
|
-
}
|
|
1343
|
-
function formatScratchFilesystems(checks) {
|
|
1344
|
-
if (checks.length === 0) {
|
|
1345
|
-
return " \u26A0 Scratch space: no scratch filesystem could be identified";
|
|
1346
|
-
}
|
|
1347
|
-
return checks.map(formatScratchSpace).join("\n");
|
|
1348
|
-
}
|
|
1349
|
-
function formatScratchSpace(check) {
|
|
1350
|
-
if (!check.available) {
|
|
1351
|
-
return ` \u26A0 Scratch space [${check.label}]: ${check.message}`;
|
|
1352
|
-
}
|
|
1353
|
-
const icon = check.severity === "ok" ? "\u2713" : check.severity === "warn" ? "\u26A0" : "\u2717";
|
|
1354
|
-
const line = ` ${icon} Scratch space [${check.label}] (${check.root}): ${check.message}`;
|
|
1355
|
-
if (check.severity === "ok") return line;
|
|
1356
|
-
return [
|
|
1357
|
-
line,
|
|
1358
|
-
" A full scratch filesystem fails subprocesses at the write step, after",
|
|
1359
|
-
" they have done their work, so output is lost rather than refused.",
|
|
1360
|
-
" Free space, or point NEXUS_TMPDIR at a roomier filesystem."
|
|
1361
|
-
].join("\n");
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
// src/mcp/server.ts
|
|
1365
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
1366
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1367
|
-
|
|
1368
|
-
// src/mcp/task-store.ts
|
|
1369
|
-
import { InMemoryTaskStore } from "@modelcontextprotocol/sdk/experimental/tasks";
|
|
1370
|
-
var MAX_TASK_TTL_MS = 6e5;
|
|
1371
|
-
var DEFAULT_TASK_TTL_MS = 3e5;
|
|
1372
|
-
var MAX_TASK_CAPACITY = 50;
|
|
1373
|
-
var CLEANUP_INTERVAL_MS = 6e4;
|
|
1374
|
-
var logger = createLogger({ component: "task-store" });
|
|
1375
|
-
var singletonStore;
|
|
1376
|
-
var cleanupTimer;
|
|
1377
|
-
function getTaskStore() {
|
|
1378
|
-
if (singletonStore === void 0) {
|
|
1379
|
-
singletonStore = new InMemoryTaskStore();
|
|
1380
|
-
logger.info("Task store created", { maxCapacity: MAX_TASK_CAPACITY });
|
|
1381
|
-
cleanupTimer = setInterval(() => {
|
|
1382
|
-
evictExcessTasks();
|
|
1383
|
-
}, CLEANUP_INTERVAL_MS);
|
|
1384
|
-
if (typeof cleanupTimer === "object" && "unref" in cleanupTimer) {
|
|
1385
|
-
cleanupTimer.unref();
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
return singletonStore;
|
|
1389
|
-
}
|
|
1390
|
-
function clampTaskTtl(requestedTtl) {
|
|
1391
|
-
if (requestedTtl === void 0 || requestedTtl === null) {
|
|
1392
|
-
return DEFAULT_TASK_TTL_MS;
|
|
1393
|
-
}
|
|
1394
|
-
if (requestedTtl > MAX_TASK_TTL_MS) {
|
|
1395
|
-
logger.warn("Task TTL clamped to maximum", {
|
|
1396
|
-
requested: requestedTtl,
|
|
1397
|
-
max: MAX_TASK_TTL_MS
|
|
1398
|
-
});
|
|
1399
|
-
return MAX_TASK_TTL_MS;
|
|
1400
|
-
}
|
|
1401
|
-
return requestedTtl;
|
|
1402
|
-
}
|
|
1403
|
-
function evictExcessTasks() {
|
|
1404
|
-
if (singletonStore === void 0) return;
|
|
1405
|
-
const allTasks = singletonStore.getAllTasks();
|
|
1406
|
-
if (allTasks.length <= MAX_TASK_CAPACITY) return;
|
|
1407
|
-
const sorted = [...allTasks].sort(
|
|
1408
|
-
(a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
|
|
1409
|
-
);
|
|
1410
|
-
const evictCount = sorted.length - MAX_TASK_CAPACITY;
|
|
1411
|
-
for (let i = 0; i < evictCount; i++) {
|
|
1412
|
-
const task = sorted[i];
|
|
1413
|
-
if (task === void 0) continue;
|
|
1414
|
-
singletonStore.updateTaskStatus(task.taskId, "cancelled", "Evicted: capacity exceeded").catch((err2) => {
|
|
1415
|
-
logger.debug("Failed to evict task", {
|
|
1416
|
-
taskId: task.taskId,
|
|
1417
|
-
error: String(err2)
|
|
1418
|
-
});
|
|
1419
|
-
});
|
|
1420
|
-
}
|
|
1421
|
-
logger.info("Evicted excess tasks", { evicted: evictCount, total: sorted.length });
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
// src/mcp/server.ts
|
|
1425
|
-
var DEFAULT_SERVER_NAME = "nexus-agents";
|
|
1426
|
-
function createServerError(code, message, error) {
|
|
1427
|
-
const serverError = { code, message };
|
|
1428
|
-
if (error instanceof Error) {
|
|
1429
|
-
serverError.cause = error;
|
|
1430
|
-
}
|
|
1431
|
-
return serverError;
|
|
1432
|
-
}
|
|
1433
|
-
function createServer(config) {
|
|
1434
|
-
const serverName = config?.name ?? DEFAULT_SERVER_NAME;
|
|
1435
|
-
const serverVersion = config?.version ?? VERSION;
|
|
1436
|
-
const logger2 = config?.logger ?? createLogger({ component: "mcp-server" });
|
|
1437
|
-
try {
|
|
1438
|
-
initDataDirectories();
|
|
1439
|
-
logger2.info("Creating MCP server", {
|
|
1440
|
-
name: serverName,
|
|
1441
|
-
version: serverVersion
|
|
1442
|
-
});
|
|
1443
|
-
const server = new McpServer(
|
|
1444
|
-
{
|
|
1445
|
-
name: serverName,
|
|
1446
|
-
version: serverVersion
|
|
1447
|
-
},
|
|
1448
|
-
{
|
|
1449
|
-
capabilities: {
|
|
1450
|
-
logging: {},
|
|
1451
|
-
prompts: {},
|
|
1452
|
-
resources: {},
|
|
1453
|
-
tasks: {}
|
|
1454
|
-
},
|
|
1455
|
-
taskStore: getTaskStore()
|
|
1456
|
-
}
|
|
1457
|
-
);
|
|
1458
|
-
logger2.debug("MCP server created successfully");
|
|
1459
|
-
return ok({ server, logger: logger2 });
|
|
1460
|
-
} catch (error) {
|
|
1461
|
-
const errorMessage = getErrorMessage(error);
|
|
1462
|
-
logger2.error("Failed to create MCP server", error instanceof Error ? error : void 0);
|
|
1463
|
-
return err(
|
|
1464
|
-
createServerError(
|
|
1465
|
-
"SERVER_CREATION_FAILED",
|
|
1466
|
-
`Failed to create MCP server: ${errorMessage}`,
|
|
1467
|
-
error
|
|
1468
|
-
)
|
|
1469
|
-
);
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
async function connectTransport(server, transport, logger2) {
|
|
1473
|
-
const log = logger2 ?? createLogger({ component: "mcp-server" });
|
|
1474
|
-
try {
|
|
1475
|
-
log.info("Connecting server to transport");
|
|
1476
|
-
await server.connect(transport);
|
|
1477
|
-
log.debug("Server connected to transport successfully");
|
|
1478
|
-
return ok(void 0);
|
|
1479
|
-
} catch (error) {
|
|
1480
|
-
const errorMessage = getErrorMessage(error);
|
|
1481
|
-
log.error("Failed to connect to transport", error instanceof Error ? error : void 0);
|
|
1482
|
-
return err(
|
|
1483
|
-
createServerError(
|
|
1484
|
-
"SERVER_START_FAILED",
|
|
1485
|
-
`Failed to connect to transport: ${errorMessage}`,
|
|
1486
|
-
error
|
|
1487
|
-
)
|
|
1488
|
-
);
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
async function startStdioServer(config) {
|
|
1492
|
-
const serverResult = createServer(config);
|
|
1493
|
-
if (!serverResult.ok) {
|
|
1494
|
-
return serverResult;
|
|
1495
|
-
}
|
|
1496
|
-
const { server, logger: logger2 } = serverResult.value;
|
|
1497
|
-
try {
|
|
1498
|
-
logger2.setDestination?.("stderr");
|
|
1499
|
-
logger2.info("Starting stdio transport");
|
|
1500
|
-
const transport = new StdioServerTransport();
|
|
1501
|
-
const connectResult = await connectTransport(server, transport, logger2);
|
|
1502
|
-
if (!connectResult.ok) {
|
|
1503
|
-
return connectResult;
|
|
1504
|
-
}
|
|
1505
|
-
logger2.info("MCP server running with stdio transport");
|
|
1506
|
-
return ok({ server, logger: logger2 });
|
|
1507
|
-
} catch (error) {
|
|
1508
|
-
const errorMessage = getErrorMessage(error);
|
|
1509
|
-
logger2.error("Failed to start stdio server", error instanceof Error ? error : void 0);
|
|
1510
|
-
return err(
|
|
1511
|
-
createServerError(
|
|
1512
|
-
"SERVER_START_FAILED",
|
|
1513
|
-
`Failed to start stdio server: ${errorMessage}`,
|
|
1514
|
-
error
|
|
1515
|
-
)
|
|
1516
|
-
);
|
|
1517
|
-
}
|
|
1518
|
-
}
|
|
1519
|
-
async function closeServer(server, logger2) {
|
|
1520
|
-
const log = logger2 ?? createLogger({ component: "mcp-server" });
|
|
1521
|
-
try {
|
|
1522
|
-
log.info("Closing MCP server");
|
|
1523
|
-
await server.close();
|
|
1524
|
-
log.info("MCP server closed successfully");
|
|
1525
|
-
return ok(void 0);
|
|
1526
|
-
} catch (error) {
|
|
1527
|
-
const errorMessage = getErrorMessage(error);
|
|
1528
|
-
log.error("Failed to close server", error instanceof Error ? error : void 0);
|
|
1529
|
-
return err(
|
|
1530
|
-
createServerError("SERVER_STOP_FAILED", `Failed to close server: ${errorMessage}`, error)
|
|
1531
|
-
);
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
|
|
1535
|
-
// src/cli/doctor-formatting.ts
|
|
1536
|
-
var REQUIRED_NODE_MAJOR = 22;
|
|
1537
|
-
function formatStatus(healthy, warn = false) {
|
|
1538
|
-
if (healthy) return `${colors.green}${symbols.check}${colors.reset}`;
|
|
1539
|
-
if (warn) return `${colors.yellow}${symbols.warn}${colors.reset}`;
|
|
1540
|
-
return `${colors.red}${symbols.cross}${colors.reset}`;
|
|
1541
|
-
}
|
|
1542
|
-
function formatVersionStatus(status) {
|
|
1543
|
-
switch (status) {
|
|
1544
|
-
case "supported":
|
|
1545
|
-
return `${colors.green}supported${colors.reset}`;
|
|
1546
|
-
case "outdated":
|
|
1547
|
-
return `${colors.yellow}outdated${colors.reset}`;
|
|
1548
|
-
case "unsupported":
|
|
1549
|
-
case "breaking":
|
|
1550
|
-
return `${colors.red}${status}${colors.reset}`;
|
|
1551
|
-
default:
|
|
1552
|
-
return status;
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
function formatCapacity(capacity) {
|
|
1556
|
-
if (capacity === void 0) return "Unknown";
|
|
1557
|
-
if (!capacity.observed) {
|
|
1558
|
-
return `${colors.yellow}unknown (no usage observed this session)${colors.reset}`;
|
|
1559
|
-
}
|
|
1560
|
-
const remaining = 100 - capacity.utilizationPercent;
|
|
1561
|
-
const remainingStr = String(remaining);
|
|
1562
|
-
if (remaining > 80) return `${colors.green}${remainingStr}% remaining${colors.reset}`;
|
|
1563
|
-
if (remaining > 20) return `${colors.yellow}${remainingStr}% remaining${colors.reset}`;
|
|
1564
|
-
return `${colors.red}${remainingStr}% remaining${colors.reset}`;
|
|
1565
|
-
}
|
|
1566
|
-
function printInstalledCliDetails(cli) {
|
|
1567
|
-
writeLine(` Version: ${cli.version} (${formatVersionStatus(cli.versionStatus)})`);
|
|
1568
|
-
const authText = cli.authState === "authenticated" ? `${colors.green}${cli.authMethod ?? "Authenticated"}${colors.reset}` : cli.authState === "unverified" ? `${colors.yellow}unverified (no non-interactive auth check)${colors.reset}` : `${colors.red}Not authenticated${colors.reset}`;
|
|
1569
|
-
writeLine(` Auth: ${authText}`);
|
|
1570
|
-
if (cli.capacity !== void 0) {
|
|
1571
|
-
writeLine(` Capacity: ${formatCapacity(cli.capacity)}`);
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
function printCliResult(cli) {
|
|
1575
|
-
const status = cli.installed && cli.authenticated;
|
|
1576
|
-
const warn = cli.installed && (!cli.authenticated || cli.versionStatus === "outdated");
|
|
1577
|
-
writeLine(
|
|
1578
|
-
`${formatStatus(status, warn)} ${colors.bold}${capitalize(cli.name)} CLI${colors.reset}`
|
|
1579
|
-
);
|
|
1580
|
-
if (cli.installed) {
|
|
1581
|
-
printInstalledCliDetails(cli);
|
|
1582
|
-
} else {
|
|
1583
|
-
const errorText = cli.error ?? "Not installed";
|
|
1584
|
-
writeLine(` ${colors.red}Error: ${errorText}${colors.reset}`);
|
|
1585
|
-
}
|
|
1586
|
-
if (cli.fix !== void 0 && cli.fix !== "") {
|
|
1587
|
-
writeLine(` ${colors.dim}Fix: ${cli.fix}${colors.reset}`);
|
|
1588
|
-
}
|
|
1589
|
-
writeLine("");
|
|
1590
|
-
}
|
|
1591
|
-
function printCapabilities(clis) {
|
|
1592
|
-
const installedClis = clis.filter((c) => c.installed);
|
|
1593
|
-
if (installedClis.length === 0) {
|
|
1594
|
-
writeLine(`${formatStatus(false)} No CLIs installed`);
|
|
1595
|
-
return;
|
|
1596
|
-
}
|
|
1597
|
-
const caps = DEFAULT_CAPABILITIES;
|
|
1598
|
-
const bestReasoning = installedClis.reduce(
|
|
1599
|
-
(best, c) => caps[c.name].reasoning > caps[best.name].reasoning ? c : best
|
|
1600
|
-
);
|
|
1601
|
-
const bestContext = installedClis.reduce(
|
|
1602
|
-
(best, c) => caps[c.name].contextWindow > caps[best.name].contextWindow ? c : best
|
|
1603
|
-
);
|
|
1604
|
-
const bestSpeed = installedClis.reduce(
|
|
1605
|
-
(best, c) => caps[c.name].speed > caps[best.name].speed ? c : best
|
|
1606
|
-
);
|
|
1607
|
-
const contextTokensK = (caps[bestContext.name].contextWindow / 1e3).toFixed(0);
|
|
1608
|
-
writeLine(
|
|
1609
|
-
`${formatStatus(true)} Complex reasoning: ${colors.bold}${capitalize(bestReasoning.name)}${colors.reset}`
|
|
1610
|
-
);
|
|
1611
|
-
writeLine(
|
|
1612
|
-
`${formatStatus(true)} Large context: ${colors.bold}${capitalize(bestContext.name)}${colors.reset} (${contextTokensK}K tokens)`
|
|
1613
|
-
);
|
|
1614
|
-
writeLine(
|
|
1615
|
-
`${formatStatus(true)} Fast execution: ${colors.bold}${capitalize(bestSpeed.name)}${colors.reset}`
|
|
1616
|
-
);
|
|
1617
|
-
}
|
|
1618
|
-
function printNodeVersionCheck(check) {
|
|
1619
|
-
const versionText = check.supported ? `${colors.green}${check.version}${colors.reset}` : `${colors.yellow}${check.version}${colors.reset}`;
|
|
1620
|
-
writeLine(`${formatStatus(check.supported, !check.supported)} Node.js version: ${versionText}`);
|
|
1621
|
-
if (!check.supported) {
|
|
1622
|
-
writeLine(
|
|
1623
|
-
` ${colors.dim}Warning: Node.js ${String(REQUIRED_NODE_MAJOR)}.x LTS required${colors.reset}`
|
|
1624
|
-
);
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
function printApiKeysCheck(keys) {
|
|
1628
|
-
const configuredCount = keys.filter((k) => k.configured).length;
|
|
1629
|
-
const configuredNames = keys.filter((k) => k.configured).map((k) => k.name);
|
|
1630
|
-
const hasAny = configuredCount > 0;
|
|
1631
|
-
writeLine(
|
|
1632
|
-
`${formatStatus(hasAny, !hasAny)} API keys configured: ${String(configuredCount)} of ${String(keys.length)}`
|
|
1633
|
-
);
|
|
1634
|
-
if (hasAny) {
|
|
1635
|
-
writeLine(` ${colors.dim}Keys: ${configuredNames.join(", ")}${colors.reset}`);
|
|
1636
|
-
} else {
|
|
1637
|
-
writeLine(
|
|
1638
|
-
` ${colors.dim}Set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_AI_API_KEY${colors.reset}`
|
|
1639
|
-
);
|
|
1640
|
-
}
|
|
1641
|
-
}
|
|
1642
|
-
function printConfigFileCheck(check) {
|
|
1643
|
-
if (check.found && check.path !== null) {
|
|
1644
|
-
writeLine(`${formatStatus(true)} Configuration loaded: ${check.path}`);
|
|
1645
|
-
} else {
|
|
1646
|
-
writeLine(`${formatStatus(false, true)} Configuration file: Not found`);
|
|
1647
|
-
writeLine(` ${colors.dim}Run: nexus-agents config init${colors.reset}`);
|
|
1648
|
-
}
|
|
1649
|
-
}
|
|
1650
|
-
function printRegistryAdvisory(advisory) {
|
|
1651
|
-
const allAvailable = advisory.unavailableModels === 0;
|
|
1652
|
-
const countText = `${String(advisory.availableModels)} of ${String(advisory.totalModels)}`;
|
|
1653
|
-
writeLine(`${formatStatus(allAvailable, !allAvailable)} Models available: ${countText}`);
|
|
1654
|
-
if (advisory.unavailableModels > 0) {
|
|
1655
|
-
const missing = advisory.models.filter((m) => !m.available);
|
|
1656
|
-
for (const m of missing) {
|
|
1657
|
-
writeLine(` ${colors.dim}${m.displayName} \u2014 ${m.reason}${colors.reset}`);
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
const ageText = `${String(advisory.registryAgeDays)} days old`;
|
|
1661
|
-
if (advisory.registryStale) {
|
|
1662
|
-
writeLine(
|
|
1663
|
-
`${colors.yellow}${symbols.warn}${colors.reset} Model registry is ${ageText} \u2014 may have stale model data`
|
|
1664
|
-
);
|
|
1665
|
-
writeLine(
|
|
1666
|
-
` ${colors.dim}Update with 'npm update -g nexus-agents' to pick up the latest registry,${colors.reset}`
|
|
1667
|
-
);
|
|
1668
|
-
writeLine(
|
|
1669
|
-
` ${colors.dim}or run 'nexus-agents registry refresh' to probe currently-installed models.${colors.reset}`
|
|
1670
|
-
);
|
|
1671
|
-
} else {
|
|
1672
|
-
writeLine(`${formatStatus(true)} Model registry: ${ageText}`);
|
|
1673
|
-
}
|
|
1674
|
-
}
|
|
1675
|
-
function printLearningPersistence(check) {
|
|
1676
|
-
if (!check.enabled) {
|
|
1677
|
-
writeLine(`${formatStatus(true)} Learning persistence: ${colors.dim}Disabled${colors.reset}`);
|
|
1678
|
-
writeLine(` ${colors.dim}Set NEXUS_PERSIST_LEARNING=true to enable${colors.reset}`);
|
|
1679
|
-
return;
|
|
1680
|
-
}
|
|
1681
|
-
const healthy = check.dirExists && check.dirWritable && check.error === null;
|
|
1682
|
-
writeLine(`${formatStatus(healthy, !healthy)} Learning persistence: Enabled`);
|
|
1683
|
-
if (check.error !== null) {
|
|
1684
|
-
writeLine(` ${colors.red}Error: ${check.error}${colors.reset}`);
|
|
1685
|
-
return;
|
|
1686
|
-
}
|
|
1687
|
-
const dirStatus = check.dirExists ? check.dirWritable ? `${colors.green}writable${colors.reset}` : `${colors.red}not writable${colors.reset}` : `${colors.yellow}not created yet${colors.reset}`;
|
|
1688
|
-
writeLine(` Data directory: ${dirStatus}`);
|
|
1689
|
-
writeLine(` Outcomes: ${String(check.outcomeCount)} recorded`);
|
|
1690
|
-
writeLine(` Distilled rules: ${String(check.ruleCount)} active`);
|
|
1691
|
-
if (check.rulesLastSaved !== null) {
|
|
1692
|
-
writeLine(` Rules last saved: ${check.rulesLastSaved}`);
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
|
-
function printSqliteCheck(check) {
|
|
1696
|
-
if (check.available) {
|
|
1697
|
-
writeLine(
|
|
1698
|
-
`${formatStatus(true)} SQLite (better-sqlite3): ${colors.green}Available${colors.reset}`
|
|
1699
|
-
);
|
|
1700
|
-
} else {
|
|
1701
|
-
writeLine(
|
|
1702
|
-
`${formatStatus(false, true)} SQLite (better-sqlite3): ${colors.yellow}Not available${colors.reset}`
|
|
1703
|
-
);
|
|
1704
|
-
writeLine(
|
|
1705
|
-
` ${colors.dim}Memory backends (agentic, adaptive, typed) require it${colors.reset}`
|
|
1706
|
-
);
|
|
1707
|
-
writeLine(` ${colors.dim}Fix: npm install -g better-sqlite3${colors.reset}`);
|
|
1708
|
-
}
|
|
1709
|
-
}
|
|
1710
|
-
function printDataDirGroup(label, rootHint, subdirs) {
|
|
1711
|
-
if (subdirs.length === 0) return;
|
|
1712
|
-
const existCount = subdirs.filter((d) => d.exists).length;
|
|
1713
|
-
writeLine(
|
|
1714
|
-
` ${colors.dim}${label} \u2014 ${rootHint} (${String(existCount)}/${String(subdirs.length)})${colors.reset}`
|
|
1715
|
-
);
|
|
1716
|
-
for (const dir of subdirs) {
|
|
1717
|
-
let marker;
|
|
1718
|
-
if (!dir.exists) marker = `${colors.dim}\xB7${colors.reset}`;
|
|
1719
|
-
else if (!dir.writable) marker = `${colors.yellow}!${colors.reset}`;
|
|
1720
|
-
else marker = `${colors.green}\u2713${colors.reset}`;
|
|
1721
|
-
const note = !dir.exists ? ` ${colors.dim}(missing \u2014 created on first use)${colors.reset}` : !dir.writable ? ` ${colors.yellow}(not writable)${colors.reset}` : "";
|
|
1722
|
-
writeLine(` ${marker} ${dir.name} ${colors.dim}${dir.path}${colors.reset}${note}`);
|
|
1723
|
-
}
|
|
1724
|
-
}
|
|
1725
|
-
function printDataDirectory(check) {
|
|
1726
|
-
const allExist = allOf(check.subdirectories, (d) => d.exists, false);
|
|
1727
|
-
const allWritable = allOf(check.subdirectories, (d) => !d.exists || d.writable, false);
|
|
1728
|
-
const healthy = check.rootExists && allWritable;
|
|
1729
|
-
writeLine(`${formatStatus(healthy, !healthy)} Data directory layout:`);
|
|
1730
|
-
const perRepo = check.subdirectories.filter((d) => d.scope === "per-repo");
|
|
1731
|
-
const crossRepo = check.subdirectories.filter((d) => d.scope === "cross-repo");
|
|
1732
|
-
if (check.repoRoot !== null) {
|
|
1733
|
-
printDataDirGroup("Per-repo", check.repoRoot, perRepo);
|
|
1734
|
-
} else {
|
|
1735
|
-
printDataDirGroup("Per-repo (homedir \u2014 repo-preferred off)", check.rootPath, perRepo);
|
|
1736
|
-
}
|
|
1737
|
-
printDataDirGroup("Cross-repo", check.rootPath, crossRepo);
|
|
1738
|
-
if (!allExist) {
|
|
1739
|
-
writeLine(` ${colors.dim}Fix: nexus-agents setup${colors.reset}`);
|
|
1740
|
-
}
|
|
1741
|
-
}
|
|
1742
|
-
function printSandboxHeader(check) {
|
|
1743
|
-
if (check.active) {
|
|
1744
|
-
writeLine(`${formatStatus(true)} Sandbox flavor: ${check.flavor ?? "(unknown)"}`);
|
|
1745
|
-
writeLine(` ${colors.dim}NEXUS_SANDBOX_ROOT: ${check.root ?? "(unset)"}${colors.reset}`);
|
|
1746
|
-
return;
|
|
1747
|
-
}
|
|
1748
|
-
writeLine(
|
|
1749
|
-
`${formatStatus(false, true)} Container detected (${check.heuristicMatch ?? "unknown"}) but ${colors.yellow}NEXUS_SANDBOX is unset${colors.reset}`
|
|
1750
|
-
);
|
|
1751
|
-
writeLine(
|
|
1752
|
-
` ${colors.dim}Set NEXUS_SANDBOX=<flavor> in the image to opt into sandbox-aware behaviour.${colors.reset}`
|
|
1753
|
-
);
|
|
1754
|
-
}
|
|
1755
|
-
function printSandboxWarnings(check) {
|
|
1756
|
-
if (check.mismatch && check.active) {
|
|
1757
|
-
writeLine(
|
|
1758
|
-
` ${colors.yellow}Mismatch: NEXUS_SANDBOX is set but no container heuristic matched (heuristic=${String(check.heuristicMatch)}).${colors.reset}`
|
|
1759
|
-
);
|
|
1760
|
-
}
|
|
1761
|
-
if (check.dataDirInsideRepo) {
|
|
1762
|
-
writeLine(
|
|
1763
|
-
` ${colors.yellow}NEXUS_DATA_DIR resolves inside a single repo subfolder of NEXUS_SANDBOX_ROOT \u2014 state will be lost when switching repos. Set NEXUS_DATA_DIR at the multi-repo root.${colors.reset}`
|
|
1764
|
-
);
|
|
1765
|
-
}
|
|
1766
|
-
}
|
|
1767
|
-
function printSandbox(check) {
|
|
1768
|
-
const heuristicSaysContainer = check.heuristicMatch === "docker" || check.heuristicMatch === "podman";
|
|
1769
|
-
if (!check.active && !heuristicSaysContainer) {
|
|
1770
|
-
return;
|
|
1771
|
-
}
|
|
1772
|
-
writeLine(`${colors.cyan}Checking sandbox awareness...${colors.reset}`);
|
|
1773
|
-
writeLine("");
|
|
1774
|
-
printSandboxHeader(check);
|
|
1775
|
-
printSandboxWarnings(check);
|
|
1776
|
-
writeLine("");
|
|
1777
|
-
}
|
|
1778
|
-
function printDoctorSummary(result) {
|
|
1779
|
-
const unhealthyCount = result.clis.filter((c) => !c.installed || !c.authenticated).length;
|
|
1780
|
-
const nodeIssue = result.nodeVersion.supported ? 0 : 1;
|
|
1781
|
-
const totalIssues = unhealthyCount + nodeIssue + (result.mcpServerReady ? 0 : 1);
|
|
1782
|
-
const summary = result.allHealthy ? `${colors.green}${colors.bold}Status: Ready${colors.reset}` : `${colors.yellow}${colors.bold}Summary: ${String(totalIssues)} issue(s) found${colors.reset}`;
|
|
1783
|
-
writeLine(summary);
|
|
1784
|
-
writeLine("");
|
|
1785
|
-
}
|
|
1786
|
-
function printVoterTransportCheck(check) {
|
|
1787
|
-
if (check.configured) {
|
|
1788
|
-
writeLine(`${formatStatus(true)} Voter transport: In-process gateway`);
|
|
1789
|
-
return;
|
|
1790
|
-
}
|
|
1791
|
-
writeLine(`${formatStatus(true)} Voter transport: ${colors.dim}CLI subprocess${colors.reset}`);
|
|
1792
|
-
writeLine(
|
|
1793
|
-
` ${colors.dim}Set NEXUS_OPENAI_COMPAT_URL and NEXUS_OPENAI_COMPAT_KEY for faster in-process voting${colors.reset}`
|
|
1794
|
-
);
|
|
1795
|
-
}
|
|
1796
|
-
function printDoctorResults(result) {
|
|
1797
|
-
writeLine("");
|
|
1798
|
-
writeLine(`${colors.bold}Nexus Agents Doctor${colors.reset}`);
|
|
1799
|
-
writeLine("===================");
|
|
1800
|
-
writeLine("");
|
|
1801
|
-
writeLine(`${colors.cyan}Checking environment...${colors.reset}`);
|
|
1802
|
-
writeLine("");
|
|
1803
|
-
printNodeVersionCheck(result.nodeVersion);
|
|
1804
|
-
printApiKeysCheck(result.apiKeys);
|
|
1805
|
-
printConfigFileCheck(result.configFile);
|
|
1806
|
-
writeLine("");
|
|
1807
|
-
writeLine(`${colors.cyan}Checking CLI installations...${colors.reset}`);
|
|
1808
|
-
writeLine("");
|
|
1809
|
-
for (const cli of result.clis) {
|
|
1810
|
-
printCliResult(cli);
|
|
1811
|
-
}
|
|
1812
|
-
writeLine(`${colors.cyan}Checking MCP configuration...${colors.reset}`);
|
|
1813
|
-
writeLine("");
|
|
1814
|
-
writeLine(
|
|
1815
|
-
`${formatStatus(result.mcpServerReady)} MCP Server mode: ${result.mcpServerReady ? "Ready" : "Not ready"}`
|
|
1816
|
-
);
|
|
1817
|
-
writeLine(
|
|
1818
|
-
`${formatStatus(result.mcpClientReady)} MCP Client mode: ${result.mcpClientReady ? "Ready (Codex mcp-server)" : "Not ready (Codex not installed)"}`
|
|
1819
|
-
);
|
|
1820
|
-
printVoterTransportCheck(result.voterTransport);
|
|
1821
|
-
writeLine("");
|
|
1822
|
-
writeLine(`${colors.cyan}Checking capabilities...${colors.reset}`);
|
|
1823
|
-
writeLine("");
|
|
1824
|
-
printCapabilities(result.clis);
|
|
1825
|
-
writeLine("");
|
|
1826
|
-
writeLine(`${colors.cyan}Checking model registry...${colors.reset}`);
|
|
1827
|
-
writeLine("");
|
|
1828
|
-
printRegistryAdvisory(result.registryAdvisory);
|
|
1829
|
-
writeLine("");
|
|
1830
|
-
writeLine(`${colors.cyan}Checking learning subsystem...${colors.reset}`);
|
|
1831
|
-
writeLine("");
|
|
1832
|
-
printLearningPersistence(result.learningPersistence);
|
|
1833
|
-
writeLine("");
|
|
1834
|
-
writeLine(`${colors.cyan}Checking data storage...${colors.reset}`);
|
|
1835
|
-
writeLine("");
|
|
1836
|
-
printSqliteCheck(result.sqliteCheck);
|
|
1837
|
-
printDataDirectory(result.dataDirectory);
|
|
1838
|
-
writeLine(formatScratchFilesystems(result.scratchSpace));
|
|
1839
|
-
writeLine("");
|
|
1840
|
-
printSandbox(result.sandbox);
|
|
1841
|
-
printHarnessAlignment(result.harnessAlignment);
|
|
1842
|
-
printDoctorSummary(result);
|
|
1843
|
-
}
|
|
1844
|
-
function printHarnessAlignment(check) {
|
|
1845
|
-
writeLine(`${colors.cyan}Checking agent-harness alignment...${colors.reset}`);
|
|
1846
|
-
writeLine("");
|
|
1847
|
-
writeLine(
|
|
1848
|
-
`${formatStatus(check.agentsMdExists)} AGENTS.md: ${check.agentsMdExists ? "present (federated surface)" : "MISSING \u2014 federation invariant broken"}`
|
|
1849
|
-
);
|
|
1850
|
-
for (const f of check.files) {
|
|
1851
|
-
if (!f.exists) {
|
|
1852
|
-
writeLine(` ${colors.gray}\u25CB${colors.reset} ${f.harness}: not present (${f.path})`);
|
|
1853
|
-
continue;
|
|
1854
|
-
}
|
|
1855
|
-
if (f.error !== null) {
|
|
1856
|
-
writeLine(` ${colors.red}\u2717${colors.reset} ${f.harness}: ${f.error}`);
|
|
1857
|
-
continue;
|
|
1858
|
-
}
|
|
1859
|
-
if (f.redirectsToAgentsMd) {
|
|
1860
|
-
writeLine(` ${colors.green}\u2713${colors.reset} ${f.harness}: aligned (${f.path})`);
|
|
1861
|
-
} else {
|
|
1862
|
-
writeLine(
|
|
1863
|
-
` ${colors.yellow}\u26A0${colors.reset} ${f.harness}: drift \u2014 ${f.path} exists but does NOT mention AGENTS.md`
|
|
1864
|
-
);
|
|
1865
|
-
}
|
|
1866
|
-
}
|
|
1867
|
-
writeLine("");
|
|
1868
|
-
writeLine(
|
|
1869
|
-
` Summary: ${String(check.alignedCount)} aligned, ${String(check.driftCount)} drift, ${String(check.missingCount)} absent`
|
|
1870
|
-
);
|
|
1871
|
-
if (check.driftCount > 0) {
|
|
1872
|
-
writeLine(
|
|
1873
|
-
` ${colors.yellow}Drift detected.${colors.reset} Per docs/architecture/AGENT_COMPATIBILITY.md, harness configs must redirect to AGENTS.md \u2014 never duplicate content.`
|
|
1874
|
-
);
|
|
1875
|
-
}
|
|
1876
|
-
writeLine("");
|
|
1877
|
-
}
|
|
1878
|
-
|
|
1879
|
-
// src/cli/doctor-harness-alignment.ts
|
|
1880
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
1881
|
-
import { join as join2 } from "path";
|
|
1882
|
-
var HARNESS_FILES = [
|
|
1883
|
-
{ harness: "Cursor", path: ".cursor/rules/agents.mdc" },
|
|
1884
|
-
{ harness: "Windsurf", path: ".windsurf/rules/agents.md" },
|
|
1885
|
-
{ harness: "Aider", path: ".aider.conf.yml" },
|
|
1886
|
-
{ harness: "Continue", path: ".continue/rules/agents.md" },
|
|
1887
|
-
{ harness: "Cline", path: ".clinerules/agents.md" },
|
|
1888
|
-
// Gemini CLI reads a root-level GEMINI.md natively (unlike the .rules/-dir
|
|
1889
|
-
// harnesses); it redirects to AGENTS.md per the federation (#3446 Phase 4).
|
|
1890
|
-
{ harness: "Gemini", path: "GEMINI.md" }
|
|
1891
|
-
];
|
|
1892
|
-
function checkHarnessAlignment(cwd = process.cwd()) {
|
|
1893
|
-
const agentsMdPath = join2(cwd, "AGENTS.md");
|
|
1894
|
-
const agentsMdExists = existsSync2(agentsMdPath);
|
|
1895
|
-
const files = HARNESS_FILES.map(
|
|
1896
|
-
({ harness, path }) => inspectFile(harness, path, join2(cwd, path))
|
|
1897
|
-
);
|
|
1898
|
-
const alignedCount = files.filter((f) => f.exists && f.redirectsToAgentsMd).length;
|
|
1899
|
-
const driftCount = files.filter((f) => f.exists && !f.redirectsToAgentsMd).length;
|
|
1900
|
-
const missingCount = files.filter((f) => !f.exists).length;
|
|
1901
|
-
return {
|
|
1902
|
-
agentsMdExists,
|
|
1903
|
-
files,
|
|
1904
|
-
alignedCount,
|
|
1905
|
-
driftCount,
|
|
1906
|
-
missingCount
|
|
1907
|
-
};
|
|
1908
|
-
}
|
|
1909
|
-
function inspectFile(harness, relativePath, absolutePath) {
|
|
1910
|
-
if (!existsSync2(absolutePath)) {
|
|
1911
|
-
return {
|
|
1912
|
-
harness,
|
|
1913
|
-
path: relativePath,
|
|
1914
|
-
exists: false,
|
|
1915
|
-
redirectsToAgentsMd: false,
|
|
1916
|
-
error: null
|
|
1917
|
-
};
|
|
1918
|
-
}
|
|
1919
|
-
try {
|
|
1920
|
-
const content = readFileSync2(absolutePath, "utf-8");
|
|
1921
|
-
return {
|
|
1922
|
-
harness,
|
|
1923
|
-
path: relativePath,
|
|
1924
|
-
exists: true,
|
|
1925
|
-
redirectsToAgentsMd: content.includes("AGENTS.md"),
|
|
1926
|
-
error: null
|
|
1927
|
-
};
|
|
1928
|
-
} catch (error) {
|
|
1929
|
-
return {
|
|
1930
|
-
harness,
|
|
1931
|
-
path: relativePath,
|
|
1932
|
-
exists: true,
|
|
1933
|
-
redirectsToAgentsMd: false,
|
|
1934
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1935
|
-
};
|
|
1936
|
-
}
|
|
1937
|
-
}
|
|
1938
|
-
|
|
1939
|
-
// src/cli/doctor.ts
|
|
1940
|
-
var REQUIRED_NODE_MAJOR2 = 22;
|
|
1941
|
-
var API_KEY_VARS = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GOOGLE_AI_API_KEY"];
|
|
1942
|
-
var CONFIG_FILE_PATHS = [
|
|
1943
|
-
"./.nexus-agents/nexus-agents.yaml",
|
|
1944
|
-
"./.nexus-agents/nexus-agents.yml",
|
|
1945
|
-
"./nexus-agents.yaml",
|
|
1946
|
-
"./nexus-agents.yml"
|
|
1947
|
-
];
|
|
1948
|
-
var DATA_SUBDIRECTORIES = [
|
|
1949
|
-
"memory",
|
|
1950
|
-
"memory/beliefs",
|
|
1951
|
-
"learning",
|
|
1952
|
-
"sessions",
|
|
1953
|
-
"audit",
|
|
1954
|
-
"voting",
|
|
1955
|
-
"auth",
|
|
1956
|
-
"research",
|
|
1957
|
-
"checkpoints"
|
|
1958
|
-
];
|
|
1959
|
-
function getFixCommand(name, issue) {
|
|
1960
|
-
const commands = {
|
|
1961
|
-
claude: {
|
|
1962
|
-
install: "npm install -g @anthropic-ai/claude-code",
|
|
1963
|
-
upgrade: "npm update -g @anthropic-ai/claude-code",
|
|
1964
|
-
auth: "claude auth login"
|
|
1965
|
-
},
|
|
1966
|
-
gemini: {
|
|
1967
|
-
install: "npm install -g @google/gemini-cli",
|
|
1968
|
-
upgrade: "npm update -g @google/gemini-cli",
|
|
1969
|
-
auth: "gemini auth login"
|
|
1970
|
-
},
|
|
1971
|
-
codex: {
|
|
1972
|
-
install: "npm install -g @openai/codex",
|
|
1973
|
-
upgrade: "npm update -g @openai/codex",
|
|
1974
|
-
auth: "codex auth login"
|
|
1975
|
-
},
|
|
1976
|
-
opencode: {
|
|
1977
|
-
install: "npm install -g opencode-ai",
|
|
1978
|
-
upgrade: "npm update -g opencode-ai",
|
|
1979
|
-
auth: "opencode auth login"
|
|
1980
|
-
}
|
|
1981
|
-
};
|
|
1982
|
-
return commands[name][issue] ?? "";
|
|
1983
|
-
}
|
|
1984
|
-
function createNotFoundResult(name, errorMsg) {
|
|
1985
|
-
return {
|
|
1986
|
-
name,
|
|
1987
|
-
// Not installed, so auth was never probed. `not-authenticated` would claim
|
|
1988
|
-
// a determination nobody made; the auth line is not printed for an
|
|
1989
|
-
// uninstalled CLI anyway (#4661).
|
|
1990
|
-
authState: "unverified",
|
|
1991
|
-
installed: false,
|
|
1992
|
-
version: "N/A",
|
|
1993
|
-
versionStatus: "unsupported",
|
|
1994
|
-
authenticated: false,
|
|
1995
|
-
error: errorMsg,
|
|
1996
|
-
fix: getFixCommand(name, "install")
|
|
1997
|
-
};
|
|
1998
|
-
}
|
|
1999
|
-
function detectAuthMethod(name) {
|
|
2000
|
-
const authMethods = {
|
|
2001
|
-
claude: "CLI auth",
|
|
2002
|
-
gemini: "ADC/CLI auth",
|
|
2003
|
-
codex: "CLI auth",
|
|
2004
|
-
opencode: "CLI auth"
|
|
2005
|
-
};
|
|
2006
|
-
return authMethods[name];
|
|
2007
|
-
}
|
|
2008
|
-
function resolveAuthState(authenticated, probeState) {
|
|
2009
|
-
if (authenticated) return "authenticated";
|
|
2010
|
-
return probeState === "unknown" ? "unverified" : "not-authenticated";
|
|
2011
|
-
}
|
|
2012
|
-
function createHealthyResult(name, health, authProbe, capacity) {
|
|
2013
|
-
const versionOk = health.healthy;
|
|
2014
|
-
const authenticated = versionOk && authProbe.state === "authenticated";
|
|
2015
|
-
const authState = resolveAuthState(authenticated, authProbe.state);
|
|
2016
|
-
const result = {
|
|
2017
|
-
name,
|
|
2018
|
-
installed: true,
|
|
2019
|
-
version: health.version,
|
|
2020
|
-
versionStatus: health.versionStatus,
|
|
2021
|
-
authenticated,
|
|
2022
|
-
authState,
|
|
2023
|
-
...authenticated && { authMethod: detectAuthMethod(name) },
|
|
2024
|
-
...capacity !== void 0 && { capacity }
|
|
2025
|
-
};
|
|
2026
|
-
if (health.message !== void 0 && health.message !== "") {
|
|
2027
|
-
return { ...result, error: health.message };
|
|
2028
|
-
}
|
|
2029
|
-
if (!authenticated && authProbe.state === "needs-login") {
|
|
2030
|
-
return {
|
|
2031
|
-
...result,
|
|
2032
|
-
error: authProbe.reason,
|
|
2033
|
-
fix: authProbe.fixCommand
|
|
2034
|
-
};
|
|
2035
|
-
}
|
|
2036
|
-
if (authState === "not-authenticated") {
|
|
2037
|
-
return { ...result, fix: getFixCommand(name, "auth") };
|
|
2038
|
-
}
|
|
2039
|
-
if (health.versionStatus === "outdated") {
|
|
2040
|
-
return { ...result, fix: getFixCommand(name, "upgrade") };
|
|
2041
|
-
}
|
|
2042
|
-
return result;
|
|
2043
|
-
}
|
|
2044
|
-
async function checkCli(name) {
|
|
2045
|
-
const adapters = createAllAdapters();
|
|
2046
|
-
const adapter = adapters.get(name);
|
|
2047
|
-
if (!adapter) {
|
|
2048
|
-
return createNotFoundResult(name, "Adapter not available");
|
|
2049
|
-
}
|
|
2050
|
-
try {
|
|
2051
|
-
const [health, authProbe] = await Promise.all([adapter.healthCheck(), probeCli(name)]);
|
|
2052
|
-
let capacity;
|
|
2053
|
-
try {
|
|
2054
|
-
capacity = await adapter.getCapacity();
|
|
2055
|
-
} catch (capErr) {
|
|
2056
|
-
void capErr;
|
|
2057
|
-
}
|
|
2058
|
-
return createHealthyResult(name, health, authProbe, capacity);
|
|
2059
|
-
} catch (error) {
|
|
2060
|
-
const message = getErrorMessage(error);
|
|
2061
|
-
const isNotFound = message.includes("ENOENT") || message.includes("not found");
|
|
2062
|
-
return createNotFoundResult(name, isNotFound ? "Not found in PATH" : message);
|
|
2063
|
-
}
|
|
2064
|
-
}
|
|
2065
|
-
function checkNodeVersion() {
|
|
2066
|
-
const version = process.version;
|
|
2067
|
-
const major = Number(version.slice(1).split(".")[0]);
|
|
2068
|
-
return {
|
|
2069
|
-
version,
|
|
2070
|
-
major,
|
|
2071
|
-
supported: major >= REQUIRED_NODE_MAJOR2
|
|
2072
|
-
};
|
|
2073
|
-
}
|
|
2074
|
-
function checkApiKeys() {
|
|
2075
|
-
return API_KEY_VARS.map((name) => ({
|
|
2076
|
-
name,
|
|
2077
|
-
configured: typeof process.env[name] === "string" && process.env[name] !== ""
|
|
2078
|
-
}));
|
|
2079
|
-
}
|
|
2080
|
-
function checkConfigFile() {
|
|
2081
|
-
for (const configPath of CONFIG_FILE_PATHS) {
|
|
2082
|
-
if (existsSync3(configPath)) {
|
|
2083
|
-
return { found: true, path: configPath };
|
|
2084
|
-
}
|
|
2085
|
-
}
|
|
2086
|
-
return { found: false, path: null };
|
|
2087
|
-
}
|
|
2088
|
-
function checkMcpServerReady() {
|
|
2089
|
-
try {
|
|
2090
|
-
const result = createServer({ name: "nexus-agents-doctor-check" });
|
|
2091
|
-
return result.ok;
|
|
2092
|
-
} catch {
|
|
2093
|
-
return false;
|
|
2094
|
-
}
|
|
2095
|
-
}
|
|
2096
|
-
function buildRegistryAdvisory(cliResults) {
|
|
2097
|
-
const installedClis = new Set(cliResults.filter((c) => c.installed).map((c) => c.name));
|
|
2098
|
-
const matrix = getInTreeCapabilitiesMatrix();
|
|
2099
|
-
const models = matrix.models.filter((m) => m.cliName !== void 0).map((m) => {
|
|
2100
|
-
const cliName = m.cliName ?? "";
|
|
2101
|
-
const available = cliName.length > 0 && installedClis.has(cliName);
|
|
2102
|
-
const reason = available ? `${cliName} CLI is installed` : `${cliName} CLI is not installed`;
|
|
2103
|
-
return { modelId: m.id, displayName: m.displayName, cliName, available, reason };
|
|
2104
|
-
});
|
|
2105
|
-
const STALE_THRESHOLD_DAYS = 90;
|
|
2106
|
-
const updatedAt = new Date(matrix.updatedAt);
|
|
2107
|
-
const nowMs = getTimeProvider().now();
|
|
2108
|
-
const ageDays = Math.floor((nowMs - updatedAt.getTime()) / (1e3 * 60 * 60 * 24));
|
|
2109
|
-
const isFreshInstall = !hasPriorUsage();
|
|
2110
|
-
const exceedsThreshold = ageDays > STALE_THRESHOLD_DAYS;
|
|
2111
|
-
return {
|
|
2112
|
-
totalModels: models.length,
|
|
2113
|
-
availableModels: models.filter((m) => m.available).length,
|
|
2114
|
-
unavailableModels: models.filter((m) => !m.available).length,
|
|
2115
|
-
models,
|
|
2116
|
-
registryAgeDays: ageDays,
|
|
2117
|
-
registryStale: exceedsThreshold && !isFreshInstall
|
|
2118
|
-
};
|
|
2119
|
-
}
|
|
2120
|
-
function hasPriorUsage() {
|
|
2121
|
-
try {
|
|
2122
|
-
const root = getNexusDataDir();
|
|
2123
|
-
if (!existsSync3(root)) return false;
|
|
2124
|
-
for (const sub of ["audit", "learning", "sessions", "voting"]) {
|
|
2125
|
-
const p = `${root}/${sub}`;
|
|
2126
|
-
try {
|
|
2127
|
-
if (existsSync3(p) && readdirSync(p).length > 0) return true;
|
|
2128
|
-
} catch {
|
|
2129
|
-
}
|
|
2130
|
-
}
|
|
2131
|
-
return false;
|
|
2132
|
-
} catch {
|
|
2133
|
-
return false;
|
|
2134
|
-
}
|
|
2135
|
-
}
|
|
2136
|
-
function countJsonlLines(filePath) {
|
|
2137
|
-
if (!existsSync3(filePath)) return 0;
|
|
2138
|
-
return readFileSync3(filePath, "utf-8").split("\n").filter((l) => l.trim().length > 0).length;
|
|
2139
|
-
}
|
|
2140
|
-
function readRulesMetadata(filePath) {
|
|
2141
|
-
if (!existsSync3(filePath)) return { count: 0, savedAt: null };
|
|
2142
|
-
try {
|
|
2143
|
-
const raw = JSON.parse(readFileSync3(filePath, "utf-8"));
|
|
2144
|
-
const rules = raw["rules"];
|
|
2145
|
-
const saved = raw["savedAt"];
|
|
2146
|
-
return {
|
|
2147
|
-
count: Array.isArray(rules) ? rules.length : 0,
|
|
2148
|
-
savedAt: typeof saved === "string" ? saved : null
|
|
2149
|
-
};
|
|
2150
|
-
} catch {
|
|
2151
|
-
return { count: 0, savedAt: null };
|
|
2152
|
-
}
|
|
2153
|
-
}
|
|
2154
|
-
function checkDirAccess(dir) {
|
|
2155
|
-
const exists = existsSync3(dir);
|
|
2156
|
-
if (!exists) return { exists: false, writable: false };
|
|
2157
|
-
try {
|
|
2158
|
-
accessSync(dir, fsConstants.W_OK);
|
|
2159
|
-
return { exists: true, writable: true };
|
|
2160
|
-
} catch {
|
|
2161
|
-
return { exists: true, writable: false };
|
|
2162
|
-
}
|
|
2163
|
-
}
|
|
2164
|
-
var DISABLED_CHECK = {
|
|
2165
|
-
enabled: false,
|
|
2166
|
-
dirExists: false,
|
|
2167
|
-
dirWritable: false,
|
|
2168
|
-
outcomeCount: 0,
|
|
2169
|
-
ruleCount: 0,
|
|
2170
|
-
rulesLastSaved: null,
|
|
2171
|
-
error: null
|
|
2172
|
-
};
|
|
2173
|
-
function checkLearningPersistence() {
|
|
2174
|
-
if (!isPersistenceEnabled()) return DISABLED_CHECK;
|
|
2175
|
-
try {
|
|
2176
|
-
const { exists: dirExists, writable: dirWritable } = checkDirAccess(getLearningDir());
|
|
2177
|
-
const outcomeCount = countJsonlLines(getOutcomesFile());
|
|
2178
|
-
const { count: ruleCount, savedAt: rulesLastSaved } = readRulesMetadata(getRulesFile());
|
|
2179
|
-
return {
|
|
2180
|
-
enabled: true,
|
|
2181
|
-
dirExists,
|
|
2182
|
-
dirWritable,
|
|
2183
|
-
outcomeCount,
|
|
2184
|
-
ruleCount,
|
|
2185
|
-
rulesLastSaved,
|
|
2186
|
-
error: null
|
|
2187
|
-
};
|
|
2188
|
-
} catch (error) {
|
|
2189
|
-
return {
|
|
2190
|
-
enabled: true,
|
|
2191
|
-
dirExists: false,
|
|
2192
|
-
dirWritable: false,
|
|
2193
|
-
outcomeCount: 0,
|
|
2194
|
-
ruleCount: 0,
|
|
2195
|
-
rulesLastSaved: null,
|
|
2196
|
-
error: getErrorMessage(error)
|
|
2197
|
-
};
|
|
2198
|
-
}
|
|
2199
|
-
}
|
|
2200
|
-
async function checkSqlite() {
|
|
2201
|
-
try {
|
|
2202
|
-
await import("better-sqlite3");
|
|
2203
|
-
return { available: true, error: null };
|
|
2204
|
-
} catch (error) {
|
|
2205
|
-
const msg = getErrorMessage(error);
|
|
2206
|
-
const isNotFound = msg.includes("Cannot find") || msg.includes("MODULE_NOT_FOUND");
|
|
2207
|
-
return {
|
|
2208
|
-
available: false,
|
|
2209
|
-
error: isNotFound ? "better-sqlite3 not installed \u2014 5 memory backends unavailable" : `better-sqlite3 load error: ${msg}`
|
|
2210
|
-
};
|
|
2211
|
-
}
|
|
2212
|
-
}
|
|
2213
|
-
function checkDataDirectory() {
|
|
2214
|
-
const rootPath = getNexusDataDir();
|
|
2215
|
-
const rootExists = existsSync3(rootPath);
|
|
2216
|
-
const repoRoot = getNexusRepoDir();
|
|
2217
|
-
const perRepoSet = getPerRepoSubdirs();
|
|
2218
|
-
const subdirectories = DATA_SUBDIRECTORIES.map((name) => {
|
|
2219
|
-
const segments = name.split("/");
|
|
2220
|
-
const fullPath = nexusDataPath(...segments);
|
|
2221
|
-
const exists = existsSync3(fullPath);
|
|
2222
|
-
const scope = perRepoSet.has(segments[0] ?? "") ? "per-repo" : "cross-repo";
|
|
2223
|
-
return { name, path: fullPath, scope, exists, writable: exists && isWritable(fullPath) };
|
|
2224
|
-
});
|
|
2225
|
-
return { rootExists, rootPath, repoRoot, subdirectories };
|
|
2226
|
-
}
|
|
2227
|
-
function isWritable(dirPath) {
|
|
2228
|
-
try {
|
|
2229
|
-
accessSync(dirPath, fsConstants.W_OK);
|
|
2230
|
-
return true;
|
|
2231
|
-
} catch {
|
|
2232
|
-
return false;
|
|
2233
|
-
}
|
|
2234
|
-
}
|
|
2235
|
-
function checkSandbox() {
|
|
2236
|
-
const info = detectSandbox();
|
|
2237
|
-
const dataDir = getNexusDataDir();
|
|
2238
|
-
const root = info.root;
|
|
2239
|
-
const dataDirInsideRepo = info.active && root !== void 0 && dataDir.startsWith(`${root.replace(/\/$/, "")}/`) && // path segments BETWEEN root and `.nexus-agents/` indicate the data dir
|
|
2240
|
-
// lives in a subfolder rather than at the multi-repo root.
|
|
2241
|
-
dataDir.replace(`${root.replace(/\/$/, "")}/`, "").split("/").length > 1;
|
|
2242
|
-
const heuristicSaysContainer = info.heuristicMatch === "docker" || info.heuristicMatch === "podman";
|
|
2243
|
-
const mismatch = info.active && info.heuristicMatch === "unknown" || !info.active && heuristicSaysContainer;
|
|
2244
|
-
return {
|
|
2245
|
-
active: info.active,
|
|
2246
|
-
flavor: info.flavor,
|
|
2247
|
-
root: info.root,
|
|
2248
|
-
heuristicMatch: info.heuristicMatch,
|
|
2249
|
-
mismatch,
|
|
2250
|
-
dataDirInsideRepo
|
|
2251
|
-
};
|
|
2252
|
-
}
|
|
2253
|
-
function checkVoterTransport() {
|
|
2254
|
-
return { configured: readOpenAICompatEnv() !== null };
|
|
2255
|
-
}
|
|
2256
|
-
function scratchSeverityIsAcceptable(severity) {
|
|
2257
|
-
switch (severity) {
|
|
2258
|
-
case "ok":
|
|
2259
|
-
case "warn":
|
|
2260
|
-
return true;
|
|
2261
|
-
case "critical":
|
|
2262
|
-
return false;
|
|
2263
|
-
default: {
|
|
2264
|
-
const exhaustive = severity;
|
|
2265
|
-
throw new Error(`Unhandled scratch severity: ${String(exhaustive)}`);
|
|
2266
|
-
}
|
|
2267
|
-
}
|
|
2268
|
-
}
|
|
2269
|
-
function isAllHealthy(input) {
|
|
2270
|
-
return input.nodeSupported && input.hasAuthMethod && input.mcpServerReady && scratchSeverityIsAcceptable(worstSeverity(input.scratchSpace)) && // whenEmpty = false: zero detected CLIs is not a healthy install (#4581).
|
|
2271
|
-
allOf(
|
|
2272
|
-
input.clis,
|
|
2273
|
-
(c) => c.installed && c.authenticated && c.versionStatus !== "unsupported",
|
|
2274
|
-
false
|
|
2275
|
-
);
|
|
2276
|
-
}
|
|
2277
|
-
async function runDoctor() {
|
|
2278
|
-
const clis = await Promise.all([
|
|
2279
|
-
checkCli("claude"),
|
|
2280
|
-
checkCli("gemini"),
|
|
2281
|
-
checkCli("codex"),
|
|
2282
|
-
checkCli("opencode")
|
|
2283
|
-
]);
|
|
2284
|
-
const nodeVersion = checkNodeVersion();
|
|
2285
|
-
const apiKeys = checkApiKeys();
|
|
2286
|
-
const configFile = checkConfigFile();
|
|
2287
|
-
const mcpServerReady = checkMcpServerReady();
|
|
2288
|
-
const codexCheck = clis.find((c) => c.name === "codex");
|
|
2289
|
-
const mcpClientReady = codexCheck?.installed ?? false;
|
|
2290
|
-
const registryAdvisory = buildRegistryAdvisory(clis);
|
|
2291
|
-
const learningPersistence = checkLearningPersistence();
|
|
2292
|
-
const sqliteCheck = await checkSqlite();
|
|
2293
|
-
const dataDirectory = checkDataDirectory();
|
|
2294
|
-
const sandbox = checkSandbox();
|
|
2295
|
-
const harnessAlignment = checkHarnessAlignment();
|
|
2296
|
-
const voterTransport = checkVoterTransport();
|
|
2297
|
-
const scratchSpace = checkScratchFilesystems();
|
|
2298
|
-
const hasAuthMethod = apiKeys.some((k) => k.configured) || clis.some((c) => c.installed && c.authenticated);
|
|
2299
|
-
const allHealthy = isAllHealthy({
|
|
2300
|
-
nodeSupported: nodeVersion.supported,
|
|
2301
|
-
hasAuthMethod,
|
|
2302
|
-
mcpServerReady,
|
|
2303
|
-
scratchSpace,
|
|
2304
|
-
clis
|
|
2305
|
-
});
|
|
2306
|
-
return {
|
|
2307
|
-
clis,
|
|
2308
|
-
nodeVersion,
|
|
2309
|
-
apiKeys,
|
|
2310
|
-
configFile,
|
|
2311
|
-
mcpServerReady,
|
|
2312
|
-
mcpClientReady,
|
|
2313
|
-
registryAdvisory,
|
|
2314
|
-
learningPersistence,
|
|
2315
|
-
sqliteCheck,
|
|
2316
|
-
dataDirectory,
|
|
2317
|
-
sandbox,
|
|
2318
|
-
harnessAlignment,
|
|
2319
|
-
voterTransport,
|
|
2320
|
-
scratchSpace,
|
|
2321
|
-
allHealthy,
|
|
2322
|
-
timestamp: new Date(getTimeProvider().now())
|
|
2323
|
-
};
|
|
2324
|
-
}
|
|
2325
|
-
async function doctorCommand(options = {}) {
|
|
2326
|
-
const result = await runDoctor();
|
|
2327
|
-
printDoctorResults(result);
|
|
2328
|
-
if (options.fix === true) {
|
|
2329
|
-
await runDoctorFix(result);
|
|
2330
|
-
}
|
|
2331
|
-
return result.allHealthy ? 0 : 1;
|
|
2332
|
-
}
|
|
2333
|
-
async function runDoctorFix(result) {
|
|
2334
|
-
const writeLine2 = (text) => {
|
|
2335
|
-
process.stdout.write(text + "\n");
|
|
2336
|
-
};
|
|
2337
|
-
writeLine2("");
|
|
2338
|
-
writeLine2("\x1B[1mAuto-fix\x1B[0m");
|
|
2339
|
-
writeLine2("\u2500".repeat(40));
|
|
2340
|
-
let fixCount = 0;
|
|
2341
|
-
if (!result.dataDirectory.rootExists || result.dataDirectory.subdirectories.some((d) => !d.exists || !d.writable)) {
|
|
2342
|
-
const { runSetup } = await import("./setup-command-LXMVWIKJ.js");
|
|
2343
|
-
const setupResult = runSetup({
|
|
2344
|
-
skipMcp: true,
|
|
2345
|
-
skipRules: true,
|
|
2346
|
-
skipHooks: true,
|
|
2347
|
-
skipConfig: true,
|
|
2348
|
-
skipOpencode: true
|
|
2349
|
-
});
|
|
2350
|
-
if (setupResult.success) {
|
|
2351
|
-
writeLine2("\u2713 Created missing data directories");
|
|
2352
|
-
fixCount++;
|
|
2353
|
-
}
|
|
2354
|
-
}
|
|
2355
|
-
if (!result.configFile.found) {
|
|
2356
|
-
const { runConfigInitSync } = await import("./setup-config-PJNXP57G.js");
|
|
2357
|
-
const configResult = runConfigInitSync(process.cwd(), false, false);
|
|
2358
|
-
if (configResult.success && configResult.created) {
|
|
2359
|
-
writeLine2(`\u2713 Generated config: ${configResult.path}`);
|
|
2360
|
-
fixCount++;
|
|
2361
|
-
}
|
|
2362
|
-
}
|
|
2363
|
-
if (!result.sqliteCheck.available) {
|
|
2364
|
-
writeLine2("");
|
|
2365
|
-
writeLine2("\u26A0 better-sqlite3 not installed (manual step required):");
|
|
2366
|
-
writeLine2(" npm install -g better-sqlite3");
|
|
2367
|
-
}
|
|
2368
|
-
if (fixCount > 0) {
|
|
2369
|
-
writeLine2("");
|
|
2370
|
-
writeLine2(
|
|
2371
|
-
`\x1B[32m${String(fixCount)} issue(s) fixed.\x1B[0m Re-run \x1B[1mnexus-agents doctor\x1B[0m to verify.`
|
|
2372
|
-
);
|
|
2373
|
-
} else {
|
|
2374
|
-
writeLine2("No auto-fixable issues found.");
|
|
2375
|
-
}
|
|
2376
|
-
writeLine2("");
|
|
2377
|
-
}
|
|
2378
|
-
|
|
2379
|
-
// src/cli/setup-data-dir.ts
|
|
2380
|
-
var NEXUS_DATA_DIR = getNexusDataDir();
|
|
2381
|
-
var RESTRICTED_DIRS = /* @__PURE__ */ new Set(["auth"]);
|
|
2382
|
-
function initDataDirectories(dryRun = false) {
|
|
2383
|
-
const created = [];
|
|
2384
|
-
const alreadyExisted = [];
|
|
2385
|
-
const failures = [];
|
|
2386
|
-
for (const subdir of DATA_SUBDIRECTORIES) {
|
|
2387
|
-
const mode = RESTRICTED_DIRS.has(subdir) ? 448 : void 0;
|
|
2388
|
-
const target = nexusDataPath(...subdir.split("/"));
|
|
2389
|
-
try {
|
|
2390
|
-
ensureDir(target, dryRun, created, alreadyExisted, mode);
|
|
2391
|
-
} catch (error) {
|
|
2392
|
-
failures.push(`${target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2393
|
-
}
|
|
2394
|
-
}
|
|
2395
|
-
if (failures.length > 0) {
|
|
2396
|
-
return {
|
|
2397
|
-
success: false,
|
|
2398
|
-
rootPath: NEXUS_DATA_DIR,
|
|
2399
|
-
created,
|
|
2400
|
-
alreadyExisted,
|
|
2401
|
-
error: `Failed to create ${String(failures.length)} dir(s): ${failures.join("; ")}`
|
|
2402
|
-
};
|
|
2403
|
-
}
|
|
2404
|
-
return { success: true, rootPath: NEXUS_DATA_DIR, created, alreadyExisted, error: null };
|
|
2405
|
-
}
|
|
2406
|
-
function ensureDir(dirPath, dryRun, created, alreadyExisted, mode) {
|
|
2407
|
-
if (existsSync4(dirPath)) {
|
|
2408
|
-
alreadyExisted.push(dirPath);
|
|
2409
|
-
return;
|
|
2410
|
-
}
|
|
2411
|
-
if (!dryRun) {
|
|
2412
|
-
mkdirSync(dirPath, { recursive: true, ...mode !== void 0 ? { mode } : {} });
|
|
2413
|
-
}
|
|
2414
|
-
created.push(dirPath);
|
|
2415
|
-
}
|
|
2416
|
-
|
|
2417
|
-
export {
|
|
2418
|
-
VERSION,
|
|
2419
|
-
LoggingConfigSchema,
|
|
2420
|
-
ProviderConfigSchema,
|
|
2421
|
-
ModelTiersSchema,
|
|
2422
|
-
ModelConfigSchema,
|
|
2423
|
-
WorkflowConfigSchema,
|
|
2424
|
-
VALID_EXPERT_TIERS,
|
|
2425
|
-
VALID_EXPERT_DOMAINS,
|
|
2426
|
-
MAX_SYSTEM_PROMPT_LENGTH,
|
|
2427
|
-
CustomExpertDefinitionSchema,
|
|
2428
|
-
ExpertDefinitionSchema,
|
|
2429
|
-
ExpertConfigSchema,
|
|
2430
|
-
DEFAULT_TOOL_RATE_LIMITS,
|
|
2431
|
-
SecurityConfigSchema,
|
|
2432
|
-
AppConfigSchema,
|
|
2433
|
-
defaultConfig,
|
|
2434
|
-
adaptRoutingConfig,
|
|
2435
|
-
getConfigManager,
|
|
2436
|
-
loadConfig,
|
|
2437
|
-
getConfig,
|
|
2438
|
-
levenshtein,
|
|
2439
|
-
validateNexusEnv,
|
|
2440
|
-
getKnownNexusVarNames,
|
|
2441
|
-
DEFAULT_TASK_TTL_MS,
|
|
2442
|
-
clampTaskTtl,
|
|
2443
|
-
DATA_SUBDIRECTORIES,
|
|
2444
|
-
checkApiKeys,
|
|
2445
|
-
checkSqlite,
|
|
2446
|
-
checkDataDirectory,
|
|
2447
|
-
runDoctor,
|
|
2448
|
-
doctorCommand,
|
|
2449
|
-
initDataDirectories,
|
|
2450
|
-
createServer,
|
|
2451
|
-
connectTransport,
|
|
2452
|
-
startStdioServer,
|
|
2453
|
-
closeServer
|
|
2454
|
-
};
|
|
2455
|
-
//# sourceMappingURL=chunk-XOKWIL6I.js.map
|