fix(installer): preserve healthy Python runtime and Codex connections
Server CI and Windows package / verify (push) Successful in 2m47s
Server CI and Windows package / windows-package (push) Successful in 6m58s

This commit is contained in:
yilsem committed 2026-10-13 17:22:28 +03:00
1 parent 4924781e2d
commit 049c4d1338
8 files changed
+130 -5

No files matched your search

+2
View File
@@ -35,6 +35,8 @@ jobs:
"$RUNNER_TEMP/ci-venv/bin/python" -m pytest tests/tools/test_protected_instruction_unattended.py tests/tools/test_file_write_safety.py tests/tools/test_computer_use_approval_isolation.py tests/cli/test_cli_approval_ui.py tests/cli/test_single_query_session_finalize.py -q -p no:cacheprovider -rf
- name: Codex bridge regression tests
run: '"$RUNNER_TEMP/ci-venv/bin/python" -m unittest discover -s apps/desktop/assets/codex-bridge'
- name: Managed Python update preservation
run: '"$RUNNER_TEMP/ci-venv/bin/python" -m pytest tests/test_install_ps1_reuse_healthy_venv.py tests/test_install_ps1_python_fallback_venv.py -q -p no:cacheprovider -rf'
- name: Project scoped and bounded search regressions
run: '"$RUNNER_TEMP/ci-venv/bin/python" -m pytest tests/tools/test_search_project_scope.py tests/tools/test_search_files_cpu_windows.py tests/tools/test_search_zero_match_and_multipath.py tests/tools/test_search_giant_line_containment.py tests/tools/test_search_budget_truncation.py -q -p no:cacheprovider -rf'
- name: Native watch and speech mux pytest regressions
+1 -1
View File
@@ -35,7 +35,7 @@ import gecis_context
import terminal_stream
HERE = os.path.dirname(os.path.abspath(__file__))
SURUM = "17" # AITURK IDE'nin bekledigi kopru surumu (codex-link-config.ts CODEX_BRIDGE_VERSION)
SURUM = "18" # AITURK IDE'nin bekledigi kopru surumu (codex-link-config.ts CODEX_BRIDGE_VERSION)
try:
with open(os.path.abspath(__file__), "rb") as _f:
KOD = hashlib.sha256(_f.read()).hexdigest()[:8]
+1 -1
View File
@@ -40,7 +40,7 @@ from bekci_watch import MetadataFilter
# Installed only by owned native launcher, never an HTTP control endpoint.
WATCH_OWNER = None
SURUM = "17"
SURUM = "18"
HERE = os.path.dirname(os.path.abspath(__file__))
try:
with open(os.path.abspath(__file__), "rb") as _f:
+1 -1
View File
@@ -27,7 +27,7 @@ export const CODEX_LINK_REFRESH_URL = `${CODEX_LINK_ORIGIN}/aiturk/codex-baglama
export const CODEX_LINK_MARKER = '# AITURK IDE Codex Baglama'
// Must equal SURUM in assets/codex-bridge/bridge.py; a running bridge that reports
// another version is restarted so an updated IDE never keeps an old bridge alive.
export const CODEX_BRIDGE_VERSION = '17'
export const CODEX_BRIDGE_VERSION = '18'
export const CODEX_LINK_LINE = `openai_base_url = "${CODEX_LINK_ORIGIN}/backend-api/codex" ${CODEX_LINK_MARKER}`
export const CODEX_LINK_RUN_KEY = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "aiturk-ide",
"productName": "AITURK IDE",
"private": true,
"version": "1.0.0-beta.36",
"version": "1.0.0-beta.37",
"description": "TurkServis resmi yapay zekâ geliştirme ortamı. Hermes Agent tabanlıdır.",
"author": "AITURK / TurkServis",
"repository": {
+1 -1
View File
@@ -65,7 +65,7 @@
},
"apps/desktop": {
"name": "aiturk-ide",
"version": "1.0.0-beta.36",
"version": "1.0.0-beta.37",
"license": "MIT",
"dependencies": {
"@assistant-ui/core": "0.2.23",
+46
View File
@@ -2550,6 +2550,39 @@ function Install-Repository {
Write-Success "Repository ready"
}
function Test-ReusableManagedVenv {
param([string]$VenvPath, [string]$BasePython, [string]$Version)
$pythonExe = Join-Path $VenvPath "Scripts\python.exe"
if (-not (Test-Path -LiteralPath $pythonExe -PathType Leaf)) { return $false }
$process = New-Object System.Diagnostics.Process
try {
$info = New-Object System.Diagnostics.ProcessStartInfo
$info.FileName = $pythonExe
$info.Arguments = '-I -c "import json,sys,ssl,ctypes,venv,encodings; print(json.dumps(dict(version=str(sys.version_info.major)+''.''+str(sys.version_info.minor),prefix=sys.prefix,base=sys._base_executable)))"'
$info.UseShellExecute = $false
$info.CreateNoWindow = $true
$info.RedirectStandardOutput = $true
$info.RedirectStandardError = $true
$process.StartInfo = $info
if (-not $process.Start()) { return $false }
$stdout = $process.StandardOutput.ReadToEndAsync()
$stderr = $process.StandardError.ReadToEndAsync()
if (-not $process.WaitForExit(15000)) {
try { $process.Kill() } catch { }
return $false
}
if ($process.ExitCode -ne 0) { return $false }
$health = $stdout.Result | ConvertFrom-Json
return ($health.version -eq $Version -and
[string]::Equals([IO.Path]::GetFullPath($health.prefix), [IO.Path]::GetFullPath($VenvPath), [StringComparison]::OrdinalIgnoreCase) -and
[string]::Equals([IO.Path]::GetFullPath($health.base), [IO.Path]::GetFullPath($BasePython), [StringComparison]::OrdinalIgnoreCase))
} catch {
return $false
} finally {
$process.Dispose()
}
}
function Install-Venv {
if ($NoVenv) {
Write-Info "Skipping virtual environment (-NoVenv)"
@@ -2567,6 +2600,19 @@ function Install-Venv {
throw "Hermes-managed Python is unavailable. Run install.ps1 -Stage python first."
}
# Routine source updates do not require replacing a healthy interpreter.
# Recreating it kills detached Codex bridges sharing this venv and leaves
# every subscription model disconnected until bootstrap finishes. Verify
# version, provenance and stdlib first; the dependency stage still performs
# the locked sync and import checks. Broken/mismatched runtimes retain the
# transactional repair path below.
$existingVenvPath = Join-Path $InstallDir "venv"
if (Test-ReusableManagedVenv -VenvPath $existingVenvPath -BasePython $resolvedPython.Path -Version $resolvedPython.Version) {
$env:UV_PYTHON = Join-Path $existingVenvPath "Scripts\python.exe"
Write-Success "Reusing healthy managed virtual environment; running Codex bridges preserved"
return
}
Write-Info "Creating virtual environment with Python $($resolvedPython.Version)..."
Push-Location $InstallDir
@@ -0,0 +1,77 @@
"""Run the actual installer functions against a real disposable Python venv."""
import json
import os
import shutil
import subprocess
import sys
from pathlib import Path
import pytest
SOURCE = Path(__file__).resolve().parents[1] / 'scripts/install.ps1'
PS = shutil.which('powershell') or shutil.which('pwsh')
pytestmark = pytest.mark.skipif(os.name != 'nt' or not PS, reason='Windows installer runtime test')
def function(source, name):
start = source.index('function ' + name + ' {')
opening = source.index('{', start)
depth = 0
for index in range(opening, len(source)):
if source[index] == '{': depth += 1
elif source[index] == '}': depth -= 1
if depth == 0: return source[start:index + 1]
raise AssertionError(name)
@pytest.fixture(scope='module')
def runtime(tmp_path_factory):
root = tmp_path_factory.mktemp('installer-reuse')
subprocess.run([sys.executable, '-m', 'venv', '--without-pip', str(root / 'venv')], check=True)
return root
def run(runtime, body):
text = SOURCE.read_text(encoding='utf-8-sig')
script = runtime / 'probe.ps1'
prefix = "$ErrorActionPreference = 'Stop'\n"
prefix += function(text, 'Test-ReusableManagedVenv') + '\n'
prefix += function(text, 'Install-Venv') + '\n'
prefix += "$InstallDir = '" + str(runtime).replace("'", "''") + "'\n"
prefix += "$BasePython = '" + sys._base_executable.replace("'", "''") + "'\n"
prefix += "$Version = '" + str(sys.version_info.major) + '.' + str(sys.version_info.minor) + "'\n"
script.write_text(prefix + body, encoding='utf-8-sig')
result = subprocess.run([PS, '-NoProfile', '-File', str(script)], capture_output=True, text=True, timeout=40)
assert result.returncode == 0, result.stdout + result.stderr
return result.stdout
def test_healthy_update_keeps_live_python_and_never_reaches_kill_sweep(runtime):
python = runtime / 'venv/Scripts/python.exe'
live = subprocess.Popen([str(python), '-c', 'import time;time.sleep(90)'])
try:
output = run(runtime, r'''
function Resolve-AvailablePythonVersion { return @{Path=$BasePython;Version=$Version} }
function Write-Success { param($Message); Write-Output $Message }
function Write-Info { throw 'Unexpected venv recreation/kill path' }
function taskkill { throw 'Live process must not be killed' }
Install-Venv
''')
assert 'Reusing healthy' in output
assert live.poll() is None
assert python.is_file()
finally:
live.terminate();live.wait(timeout=10)
def test_wrong_version_or_foreign_interpreter_does_not_reuse(runtime):
output = run(runtime, r'''
$venvPath = Join-Path $InstallDir 'venv'
$results = @(
(Test-ReusableManagedVenv $venvPath $BasePython '0.0'),
(Test-ReusableManagedVenv $venvPath (Join-Path $InstallDir 'other-python.exe') $Version),
(Test-ReusableManagedVenv (Join-Path $InstallDir 'missing') $BasePython $Version)
)
ConvertTo-Json -Compress -InputObject $results
''')
assert json.loads(output) == [False, False, False]