Skip to content

Commit 6f9da97

Browse files
committed
fix(ai-agent): 修复 Codex 调用与安装流程兼容性
1 parent c99b671 commit 6f9da97

5 files changed

Lines changed: 162 additions & 12 deletions

File tree

install.ps1

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -354,9 +354,11 @@ catch {
354354
Write-Warning "同步脚本失败: $($_.Exception.Message)"
355355
}
356356

357-
# 3. 构建 Bash 脚本工具集
358-
if (-not (Install-BashScripts -RootPath $ProjectRoot)) {
359-
exit 1
357+
if (-not $IsWindows) {
358+
# 3. 构建 Bash 脚本工具集
359+
if (-not (Install-BashScripts -RootPath $ProjectRoot)) {
360+
exit 1
361+
}
360362
}
361363

362364
# 4. 安装 Node.js 脚本 (跨平台)

scripts/pwsh/ai/agent-runner/agents/codex.ps1

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ function New-CodexAgentCommandSpec {
3131
$workDir = if ($Config.ContainsKey('work_dir')) { [string]$Config.work_dir } else { (Get-Location).Path }
3232
$args = New-Object 'System.Collections.Generic.List[string]'
3333
$args.Add('exec') | Out-Null
34+
$args.Add('--ephemeral') | Out-Null
3435

3536
if ($Config.ContainsKey('model')) {
3637
$args.Add('--model') | Out-Null
@@ -51,7 +52,7 @@ function New-CodexAgentCommandSpec {
5152

5253
$args.Add('-C') | Out-Null
5354
$args.Add($workDir) | Out-Null
54-
$args.Add($Prompt) | Out-Null
55+
$args.Add('-') | Out-Null
5556

56-
return New-AiAgentCommandSpec -FilePath 'codex' -ArgumentList $args.ToArray() -WorkingDirectory $workDir -Prompt $Prompt
57+
return New-AiAgentCommandSpec -FilePath 'codex' -ArgumentList $args.ToArray() -WorkingDirectory $workDir -Prompt $Prompt -InputText $Prompt
5758
}

scripts/pwsh/ai/agent-runner/core/process.ps1

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ $ErrorActionPreference = 'Stop'
2121
.PARAMETER Prompt
2222
原始 prompt 文本,仅用于统计长度,不在预览中明文打印。
2323
24+
.PARAMETER InputText
25+
需要写入外部命令 stdin 的文本。
26+
2427
.PARAMETER UnsupportedOptions
2528
当前 agent 不支持的统一配置字段。
2629
@@ -41,6 +44,8 @@ function New-AiAgentCommandSpec {
4144

4245
[string]$Prompt = '',
4346

47+
[string]$InputText = '',
48+
4449
[string[]]$UnsupportedOptions = @()
4550
)
4651

@@ -49,6 +54,7 @@ function New-AiAgentCommandSpec {
4954
ArgumentList = @($ArgumentList)
5055
WorkingDirectory = $WorkingDirectory
5156
Prompt = $Prompt
57+
InputText = $InputText
5258
UnsupportedOptions = @($UnsupportedOptions)
5359
}
5460
}
@@ -80,6 +86,9 @@ function Format-AiAgentCommandPreview {
8086
if ($arg -eq $prompt -and -not [string]::IsNullOrEmpty($prompt)) {
8187
$parts.Add('<PROMPT>') | Out-Null
8288
}
89+
elseif ($arg -eq '-' -and -not [string]::IsNullOrEmpty([string]$Spec.InputText)) {
90+
$parts.Add('<PROMPT_STDIN>') | Out-Null
91+
}
8392
else {
8493
$parts.Add([string]$arg) | Out-Null
8594
}
@@ -88,6 +97,97 @@ function Format-AiAgentCommandPreview {
8897
return ("{0} {1}`nWorkDir={2}`nPromptChars={3}" -f $Spec.FilePath, ($parts -join ' '), $Spec.WorkingDirectory, $prompt.Length)
8998
}
9099

100+
<#
101+
.SYNOPSIS
102+
解析真实调用的 agent CLI 路径。
103+
104+
.DESCRIPTION
105+
Windows 上 npm 等工具常同时生成 `.ps1` 与 `.cmd` 包装器。PowerShell 脚本再次调用 `.ps1`
106+
包装器时,可能把标准流识别成非终端并触发 agent 的 TTY 检查错误;因此执行前优先选择
107+
`.cmd`、`.exe`、`.bat` 形式的应用包装器。预览仍保留原始命令名,避免泄露本机安装路径。
108+
109+
.PARAMETER FilePath
110+
命令规格中的外部 CLI 名称或路径。
111+
112+
.OUTPUTS
113+
string
114+
返回实际用于 `&` 调用的命令路径或名称。
115+
#>
116+
function Resolve-AiAgentInvocationFilePath {
117+
[CmdletBinding()]
118+
param(
119+
[Parameter(Mandatory)]
120+
[string]$FilePath
121+
)
122+
123+
$runsOnWindows = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform(
124+
[System.Runtime.InteropServices.OSPlatform]::Windows
125+
)
126+
if (-not $runsOnWindows) {
127+
return $FilePath
128+
}
129+
130+
if ([System.IO.Path]::IsPathRooted($FilePath) -or -not [string]::IsNullOrWhiteSpace([System.IO.Path]::GetExtension($FilePath))) {
131+
return $FilePath
132+
}
133+
134+
foreach ($extension in @('.cmd', '.exe', '.bat')) {
135+
$candidate = Get-Command -Name "$FilePath$extension" -CommandType Application -ErrorAction SilentlyContinue |
136+
Select-Object -First 1
137+
if ($null -ne $candidate) {
138+
return $candidate.Source
139+
}
140+
}
141+
142+
return $FilePath
143+
}
144+
145+
<#
146+
.SYNOPSIS
147+
执行外部 agent 命令并转发输出。
148+
149+
.DESCRIPTION
150+
PowerShell 函数会把外部命令 stdout 合并到自身成功输出流。runner 顶层需要用函数返回对象读取
151+
ExitCode,因此这里把 stdout 写到 host,避免 agent 日志污染结构化返回值。
152+
153+
.PARAMETER FilePath
154+
实际调用的外部 CLI 路径或名称。
155+
156+
.PARAMETER ArgumentList
157+
传递给外部 CLI 的参数数组。
158+
159+
.PARAMETER InputText
160+
需要写入外部命令 stdin 的文本。为空时不主动写入 stdin。
161+
162+
.OUTPUTS
163+
int
164+
返回外部命令的退出码。
165+
#>
166+
function Invoke-AiAgentNativeCommand {
167+
[CmdletBinding()]
168+
param(
169+
[Parameter(Mandatory)]
170+
[string]$FilePath,
171+
172+
[string[]]$ArgumentList = @(),
173+
174+
[string]$InputText = ''
175+
)
176+
177+
if ([string]::IsNullOrEmpty($InputText)) {
178+
& $FilePath @ArgumentList | ForEach-Object {
179+
Write-Host $_
180+
}
181+
}
182+
else {
183+
$InputText | & $FilePath @ArgumentList | ForEach-Object {
184+
Write-Host $_
185+
}
186+
}
187+
188+
return $LASTEXITCODE
189+
}
190+
91191
<#
92192
.SYNOPSIS
93193
检查外部 agent CLI 是否可用。
@@ -109,7 +209,7 @@ function Assert-AiAgentCommandAvailable {
109209
[string]$FilePath
110210
)
111211

112-
if (-not (Get-Command -Name $FilePath -ErrorAction SilentlyContinue)) {
212+
if (-not (Test-Path -LiteralPath $FilePath -PathType Leaf) -and -not (Get-Command -Name $FilePath -ErrorAction SilentlyContinue)) {
113213
throw "未找到 $FilePath,请先安装并完成登录。"
114214
}
115215
}

scripts/pwsh/ai/agent-runner/main.ps1

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,12 @@ function Invoke-AiAgentRunnerCommand {
161161
return [pscustomobject]@{ ExitCode = 0; Output = Format-AiAgentCommandPreview -Spec $spec }
162162
}
163163

164-
Assert-AiAgentCommandAvailable -FilePath $spec.FilePath
164+
$invocationFilePath = Resolve-AiAgentInvocationFilePath -FilePath $spec.FilePath
165+
Assert-AiAgentCommandAvailable -FilePath $invocationFilePath
165166
Push-Location $spec.WorkingDirectory
166167
try {
167-
& $spec.FilePath @($spec.ArgumentList)
168-
return [pscustomobject]@{ ExitCode = $LASTEXITCODE; Output = '' }
168+
$exitCode = Invoke-AiAgentNativeCommand -FilePath $invocationFilePath -ArgumentList @($spec.ArgumentList) -InputText ([string]$spec.InputText)
169+
return [pscustomobject]@{ ExitCode = $exitCode; Output = '' }
169170
}
170171
finally {
171172
Pop-Location

tests/AiAgentRunner.Tests.ps1

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ Describe 'AI agent command specs' {
2828
$spec.WorkingDirectory | Should -Be '/repo'
2929
$spec.ArgumentList | Should -Be @(
3030
'exec',
31+
'--ephemeral',
3132
'--model', 'gpt-5.4',
3233
'-c', 'model_reasoning_effort="medium"',
3334
'--json',
3435
'--full-auto',
3536
'-C', '/repo',
36-
'修复测试'
37+
'-'
3738
)
39+
$spec.InputText | Should -Be '修复测试'
3840
}
3941

4042
It 'builds claude print args and ignores reasoning effort' {
@@ -82,6 +84,50 @@ Describe 'AI agent command specs' {
8284
$preview | Should -Match 'PromptChars=13'
8385
$preview | Should -Not -Match 'secret prompt'
8486
}
87+
88+
It 'prefers Windows application wrappers over PowerShell shims for invocation' -Skip:(-not $IsWindows) {
89+
$toolsDir = Join-Path $TestDrive 'agent-bin'
90+
New-Item -ItemType Directory -Path $toolsDir -Force | Out-Null
91+
Set-Content -LiteralPath (Join-Path $toolsDir 'codex.ps1') -Encoding utf8NoBOM -Value "Write-Output 'ps1'"
92+
Set-Content -LiteralPath (Join-Path $toolsDir 'codex.cmd') -Encoding ascii -Value "@echo off`r`necho cmd"
93+
94+
$originalPath = $env:PATH
95+
try {
96+
$env:PATH = "$toolsDir;$originalPath"
97+
98+
$resolved = Resolve-AiAgentInvocationFilePath -FilePath 'codex'
99+
100+
$resolved | Should -Be (Join-Path $toolsDir 'codex.cmd')
101+
}
102+
finally {
103+
$env:PATH = $originalPath
104+
}
105+
}
106+
107+
It 'returns native exit codes without adding stdout to the function result' {
108+
$pwshPath = (Get-Command pwsh -ErrorAction Stop).Source
109+
110+
$exitCode = Invoke-AiAgentNativeCommand -FilePath $pwshPath -ArgumentList @(
111+
'-NoProfile',
112+
'-Command',
113+
"Write-Output 'native output'; exit 7"
114+
) 6>$null
115+
116+
$exitCode | Should -Be 7
117+
}
118+
119+
It 'writes native stdin when input text is provided' {
120+
$pwshPath = (Get-Command pwsh -ErrorAction Stop).Source
121+
$inputText = "第一行`n第二行"
122+
123+
$exitCode = Invoke-AiAgentNativeCommand -FilePath $pwshPath -ArgumentList @(
124+
'-NoProfile',
125+
'-Command',
126+
'$text = [Console]::In.ReadToEnd(); if ($text -match "第一行" -and $text -match "第二行") { exit 0 } else { exit 9 }'
127+
) -InputText $inputText 6>$null
128+
129+
$exitCode | Should -Be 0
130+
}
85131
}
86132

87133
Describe 'AI agent runner prompt and config' {
@@ -194,7 +240,7 @@ Describe 'AI agent runner public script' {
194240
$LASTEXITCODE | Should -Be 0
195241
($output -join "`n") | Should -Match 'codex exec'
196242
($output -join "`n") | Should -Match 'model_reasoning_effort="high"'
197-
($output -join "`n") | Should -Match '<PROMPT>'
243+
($output -join "`n") | Should -Match '<PROMPT_STDIN>'
198244
($output -join "`n") | Should -Not -Match '检查当前 Git 变更'
199245
}
200246

@@ -212,7 +258,7 @@ Describe 'AI agent runner public script' {
212258
$appendChars = [int]([regex]::Match($appendText, 'PromptChars=(\d+)').Groups[1].Value)
213259

214260
$appendChars | Should -BeGreaterThan $baseChars
215-
$appendText | Should -Match '<PROMPT>'
261+
$appendText | Should -Match '<PROMPT_STDIN>'
216262
$appendText | Should -Not -Match '只提交暂存区'
217263
}
218264
}

0 commit comments

Comments
 (0)