param( [string]$TrainingDir = 'D:\qiwei-training', [string]$CredentialEnv = 'E:\workspace\QIWEI-skill\.env.local', [int]$Port = 4434 ) $ErrorActionPreference = 'Stop' function Read-EnvValue([string]$path, [string]$key) { $line = Get-Content -LiteralPath $path | Where-Object { $_ -match "^$([regex]::Escape($key))\s*=" } | Select-Object -Last 1 if (-not $line) { return '' } return ($line -replace "^$([regex]::Escape($key))\s*=\s*", '').Trim().Trim('"').Trim("'") } function Wait-Http([string]$url, [int]$seconds = 30) { $deadline = (Get-Date).AddSeconds($seconds) do { try { return Invoke-RestMethod -Uri $url -TimeoutSec 5 } catch { Start-Sleep -Milliseconds 750 } } while ((Get-Date) -lt $deadline) throw "Timed out waiting for $url" } $token = Read-EnvValue $CredentialEnv 'QIWEI_AUTH_TOKEN' if (-not $token.StartsWith('r:')) { throw 'The acceptance credential must be an r: session credential' } $root = Join-Path $env:TEMP ("qiwei-trial-" + [guid]::NewGuid().ToString('N')) $exe = Join-Path $root 'qiwei-workbench.exe' $dashboard = $null New-Item -ItemType Directory -Path $root | Out-Null try { Copy-Item (Join-Path $TrainingDir 'qiwei-workbench.exe') $exe Copy-Item (Join-Path $TrainingDir 'qiwei.runtime.config.mjs') (Join-Path $root 'qiwei.runtime.config.mjs') New-Item -ItemType Directory -Path (Join-Path $root 'web') | Out-Null Copy-Item (Join-Path $TrainingDir 'web\*') (Join-Path $root 'web') New-Item -ItemType Directory -Path (Join-Path $root 'knowledge') | Out-Null Copy-Item (Join-Path $TrainingDir 'knowledge\*') (Join-Path $root 'knowledge') -Recurse $envText = @( 'QIWEI_API_BASE=https://server.fmode.cn/api/qiwei' "QIWEI_AUTH_TOKEN=$token" 'QIWEI_UID=' 'QIWEI_GUID=' 'QIWEI_AUTO_REPLY_ALLOWED_SENDERS=' ) -join [Environment]::NewLine [IO.File]::WriteAllText((Join-Path $root '.env.local'), $envText + [Environment]::NewLine, [Text.UTF8Encoding]::new($false)) $dashboard = Start-Process -FilePath $exe -ArgumentList '--no-open','--port',"$Port" -WorkingDirectory $root -PassThru -WindowStyle Hidden -Environment @{ QIWEI_AUTH_TOKEN = 'stale-startup-credential' } $status = Wait-Http "http://127.0.0.1:$Port/api/status" Start-Sleep -Seconds 12 $agent = Invoke-RestMethod -Uri "http://127.0.0.1:$Port/api/agent/status" -TimeoutSec 10 $runtimePath = Join-Path $root 'outputs\runtime\qiwei-runtime.json' $runtime = Get-Content -LiteralPath $runtimePath -Raw | ConvertFrom-Json if (-not $status.summary.authConfigured) { throw 'Session credential was not recognized' } if (-not $status.summary.subscribed) { throw 'Trial entitlement was not recognized' } if ($status.data.subscription.summary.trialActive -ne $true) { throw 'Entitlement was not classified as trial' } if ($status.data.subscription.summary.source -ne 'trial') { throw 'Trial source was not preserved' } if ([int]$status.data.subscription.summary.seats -ne 1) { throw 'Unexpected trial seat count' } [ordered]@{ credentialKind = 'session' authConfigured = [bool]$status.summary.authConfigured subscribed = [bool]$status.summary.subscribed trialActive = [bool]$status.data.subscription.summary.trialActive source = $status.data.subscription.summary.source seats = $status.data.subscription.summary.seats usedSeats = $status.data.subscription.summary.usedSeats boundAccounts = 0 accountOnline = [bool]$agent.data.account.online listenerRunning = [bool]$agent.data.listener.running runtimeStatus = $runtime.status runtimeComponentStatus = $runtime.components.personalPolling.status runtimeError = $runtime.components.personalPolling.lastError reviewMode = $agent.data.globalMode allowedSenderCount = $agent.data.config.allowedSenderCount } | ConvertTo-Json -Depth 8 } finally { if (Test-Path $exe) { try { & $exe runtime stop *> $null } catch {} } Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -eq $exe } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue } if ($dashboard -and -not $dashboard.HasExited) { Stop-Process -Id $dashboard.Id -Force -ErrorAction SilentlyContinue } Start-Sleep -Milliseconds 500 $resolvedRoot = [IO.Path]::GetFullPath($root) $resolvedTemp = [IO.Path]::GetFullPath($env:TEMP) if ($resolvedRoot.StartsWith($resolvedTemp, [StringComparison]::OrdinalIgnoreCase) -and (Test-Path $resolvedRoot)) { Remove-Item -LiteralPath $resolvedRoot -Recurse -Force } }