@@ -484,7 +484,19 @@ export function getBundledGitBashPath(): string {
484484 * - 清除用户 npm 配置文件,避免读取用户全局设置
485485 * - 禁用 uv 自动安装到全局目录
486486 */
487- export function getAppEnv ( ) : Record < string , string > {
487+ export interface GetAppEnvOptions {
488+ /**
489+ * 是否包含系统 PATH 环境变量。
490+ * - true: 包含系统 PATH(默认行为,适用于需要访问系统工具的进程)
491+ * - false: 只包含应用内集成的 PATH(适用于 MCP 代理等需要精简环境的进程)
492+ * @default true
493+ */
494+ includeSystemPath ?: boolean ;
495+ }
496+
497+ export function getAppEnv ( opts ?: GetAppEnvOptions ) : Record < string , string > {
498+ const { includeSystemPath = true } = opts ?? { } ;
499+
488500 const appDataDir = getAppDataDir ( ) ;
489501 const nodeModulesBin = path . join ( appDataDir , "node_modules" , ".bin" ) ;
490502 const appBin = getAppBinDir ( ) ;
@@ -510,24 +522,24 @@ export function getAppEnv(): Record<string, string> {
510522 // 镜像配置
511523 const mirror = getMirrorConfig ( ) ;
512524
513- // 构建系统 PATH 的回退路径(仅包含常用系统工具目录)
514- // 这样 agent 可以使用 bash/git/grep 等系统工具
515- const systemPathPaths = getSystemPaths ( ) ;
516-
517525 // 获取内置 Node.js 24、Git 和 Electron Node 路径
518526 const bundledNodeBinDir = getBundledNodeBinDir ( ) ;
519527 const bundledGitBinDir = getBundledGitBinDir ( ) ;
520528 const bundledGitBashPath = getBundledGitBashPath ( ) ;
521529 const electronNodeBinDir = getElectronNodeBinDir ( ) ;
522530
531+ // 构建系统 PATH 的回退路径(仅包含常用系统工具目录)
532+ // 这样 agent 可以使用 bash/git/grep 等系统工具
533+ const systemPathPaths = includeSystemPath ? getSystemPaths ( ) : [ ] ;
534+
523535 // PATH 优先级:应用内 uv/uvx 优先,再应用内 node/npm,最后系统回退
524536 // - bundledNodeBinDir: 内置 Node.js 24(仅 Windows)
525537 // - bundledGitBinDir: 内置 Git bin(仅 Windows)
526538 // - electronNodeBinDir: Electron 内置的 npm/npx
527539 // - uvBin/uvToolBinDir: 应用内 uv/uvx(优先,保证 MCP 等子进程用应用内版本)
528540 // - nodeModulesBin: 应用内 node_modules/.bin
529541 // - appBin: 应用内 bin
530- // - systemPathPaths: 系统工具回退
542+ // - systemPathPaths: 系统工具回退(可选,由 includeSystemPath 控制)
531543 const priorityPath = [
532544 bundledNodeBinDir ,
533545 electronNodeBinDir ,
@@ -667,27 +679,29 @@ export function getAppEnv(): Record<string, string> {
667679 }
668680 }
669681
670- // 2. 确保 Windows 系统目录在 PATH 中
682+ // 2. 确保 Windows 系统目录在 PATH 中(始终添加,这是系统运行必需的)
671683 const windowsSystemPathEntries = [
672684 "C:\\Windows\\System32" ,
673685 "C:\\Windows\\System32\\Wbem" ,
674686 "C:\\Windows\\System32\\WindowsPowerShell\\v1.0" ,
675687 "C:\\Windows\\System32\\OpenSSH" ,
676688 ] ;
677689
678- const currentPath = cleanEnv . PATH || "" ;
690+ let currentPath = cleanEnv . PATH || "" ;
679691 const currentPathLower = currentPath . split ( ";" ) . map ( ( p ) => p . toLowerCase ( ) ) ;
680692
681693 for ( const sysPath of windowsSystemPathEntries ) {
682694 if ( ! currentPathLower . includes ( sysPath . toLowerCase ( ) ) ) {
683- cleanEnv . PATH = currentPath + ";" + sysPath ;
695+ currentPath = currentPath + ";" + sysPath ;
696+ cleanEnv . PATH = currentPath ;
684697 }
685698 }
686699
687700 // 3. 设置 ORIGINAL_PATH(POSIX 格式)供 git-bash 使用
701+ // 注意:当 includeSystemPath 为 false 时,跳过此步骤以精简环境变量
688702 // 参考 LobsterAI: 确保 git-bash 的 /etc/profile 正确处理 PATH
689703 // 注意:限制条目数量以避免超过 Windows 环境变量长度限制 (32,767)
690- if ( bundledGitBashPath ) {
704+ if ( includeSystemPath && bundledGitBashPath ) {
691705 const MAX_ORIGINAL_PATH_ENTRIES = 20 ; // 限制条目数量
692706 const pathEntries = ( cleanEnv . PATH || "" ) . split ( ";" ) . filter ( Boolean ) ;
693707 const limitedEntries = pathEntries . slice ( 0 , MAX_ORIGINAL_PATH_ENTRIES ) ;
@@ -701,62 +715,67 @@ export function getAppEnv(): Record<string, string> {
701715 }
702716
703717 // 4. 从注册表读取最新 PATH(解决用户后安装的工具不在 PATH 中的问题)
704- try {
705- const { execSync } = require ( "child_process" ) ;
706- const psScript = [
707- '$machinePath = [Environment]::GetEnvironmentVariable("Path", "Machine")' ,
708- '$userPath = [Environment]::GetEnvironmentVariable("Path", "User")' ,
709- '[Console]::Write("$machinePath;$userPath")' ,
710- ] . join ( "; " ) ;
711- const encodedCommand = Buffer . from ( psScript , "utf16le" ) . toString (
712- "base64" ,
713- ) ;
714- const result = execSync (
715- `powershell -NoProfile -NonInteractive -EncodedCommand ${ encodedCommand } ` ,
716- {
717- encoding : "utf-8" ,
718- timeout : 10000 ,
719- windowsHide : true ,
720- } ,
721- ) ;
722-
723- const registryPath = result . trim ( ) ;
724- if ( registryPath ) {
725- const registryEntries = registryPath
726- . split ( ";" )
727- . map ( ( entry : string ) => entry . trim ( ) )
728- . filter ( Boolean ) ;
729-
730- // 去重并追加到 PATH 末尾
731- // 注意:限制追加的条目数量以避免超过 Windows 环境变量长度限制
732- const MAX_REGISTRY_PATH_ENTRIES = 10 ; // 最多从注册表追加10个条目
733- const existingPaths = new Set (
734- currentPath . split ( ";" ) . map ( ( p ) => p . toLowerCase ( ) ) ,
718+ // 注意:当 includeSystemPath 为 false 时,跳过此步骤以精简环境变量
719+ if ( includeSystemPath ) {
720+ try {
721+ const { execSync } = require ( "child_process" ) ;
722+ const psScript = [
723+ '$machinePath = [Environment]::GetEnvironmentVariable("Path", "Machine")' ,
724+ '$userPath = [Environment]::GetEnvironmentVariable("Path", "User")' ,
725+ '[Console]::Write("$machinePath;$userPath")' ,
726+ ] . join ( "; " ) ;
727+ const encodedCommand = Buffer . from ( psScript , "utf16le" ) . toString (
728+ "base64" ,
735729 ) ;
736- const missingEntries : string [ ] = [ ] ;
730+ const result = execSync (
731+ `powershell -NoProfile -NonInteractive -EncodedCommand ${ encodedCommand } ` ,
732+ {
733+ encoding : "utf-8" ,
734+ timeout : 10000 ,
735+ windowsHide : true ,
736+ } ,
737+ ) ;
738+
739+ const registryPath = result . trim ( ) ;
740+ if ( registryPath ) {
741+ const registryEntries = registryPath
742+ . split ( ";" )
743+ . map ( ( entry : string ) => entry . trim ( ) )
744+ . filter ( Boolean ) ;
745+
746+ // 去重并追加到 PATH 末尾
747+ // 注意:限制追加的条目数量以避免超过 Windows 环境变量长度限制
748+ const MAX_REGISTRY_PATH_ENTRIES = 10 ; // 最多从注册表追加10个条目
749+ const existingPaths = new Set (
750+ currentPath . split ( ";" ) . map ( ( p ) => p . toLowerCase ( ) ) ,
751+ ) ;
752+ const missingEntries : string [ ] = [ ] ;
753+
754+ for ( const entry of registryEntries ) {
755+ if ( missingEntries . length >= MAX_REGISTRY_PATH_ENTRIES ) {
756+ log . info (
757+ `[getAppEnv] 已达到最大注册表 PATH 条目限制 (${ MAX_REGISTRY_PATH_ENTRIES } ),跳过剩余条目` ,
758+ ) ;
759+ break ;
760+ }
761+ if ( ! existingPaths . has ( entry . toLowerCase ( ) ) ) {
762+ missingEntries . push ( entry ) ;
763+ existingPaths . add ( entry . toLowerCase ( ) ) ;
764+ }
765+ }
737766
738- for ( const entry of registryEntries ) {
739- if ( missingEntries . length >= MAX_REGISTRY_PATH_ENTRIES ) {
767+ if ( missingEntries . length > 0 ) {
768+ cleanEnv . PATH = currentPath + ";" + missingEntries . join ( ";" ) ;
740769 log . info (
741- `[getAppEnv] 已达到最大注册表 PATH 条目限制 ( ${ MAX_REGISTRY_PATH_ENTRIES } ),跳过剩余条目 ` ,
770+ `[getAppEnv] 从注册表追加 ${ missingEntries . length } 个 PATH 条目 ` ,
742771 ) ;
743- break ;
744- }
745- if ( ! existingPaths . has ( entry . toLowerCase ( ) ) ) {
746- missingEntries . push ( entry ) ;
747- existingPaths . add ( entry . toLowerCase ( ) ) ;
748772 }
749773 }
750-
751- if ( missingEntries . length > 0 ) {
752- cleanEnv . PATH = currentPath + ";" + missingEntries . join ( ";" ) ;
753- log . info (
754- `[getAppEnv] 从注册表追加 ${ missingEntries . length } 个 PATH 条目` ,
755- ) ;
756- }
774+ } catch ( error ) {
775+ log . warn ( `[getAppEnv] 读取注册表 PATH 失败: ${ error } ` ) ;
757776 }
758- } catch ( error ) {
759- log . warn ( `[getAppEnv] 读取注册表 PATH 失败: ${ error } ` ) ;
777+ } else {
778+ log . info ( `[getAppEnv] 跳过注册表 PATH 读取(includeSystemPath=false) ` ) ;
760779 }
761780 }
762781
0 commit comments