diff --git a/install-windows.ps1 b/install-windows.ps1 index 5a878d6..f26e0b8 100644 --- a/install-windows.ps1 +++ b/install-windows.ps1 @@ -106,64 +106,6 @@ if (-not (Test-Path (Join-Path $RepoRoot "interface\web\dist\index.html"))) { } Write-OK "Web UI built (interface\web\dist)" -# -- Pull models (best-effort) ------------------------------------------------- -# Which models ship is decided in ONE place - DEFAULT_CHAT_MODEL and -# DEFAULT_MEMORY_MODEL in synapse\nexus_config.py (rationale documented there). -# Read them instead of hardcoding, so the installer can never pull one model -# while the backend defaults to another. -Push-Location $RepoRoot -$ChatModel = (& $VenvPy -c "from synapse.nexus_config import DEFAULT_CHAT_MODEL as m; print(m)") -$MemModel = (& $VenvPy -c "from synapse.nexus_config import DEFAULT_MEMORY_MODEL as m; print(m)") -Pop-Location -if ($LASTEXITCODE -ne 0 -or -not $ChatModel -or -not $MemModel) { - Write-Fail "Could not read the default models from synapse\nexus_config.py - the venv install is broken" -} - -Write-Step "Pulling models ($ChatModel for chat, $MemModel for memory)" -Write-Host " Downloads a few GB; press Ctrl+C to skip and pull them later from the Models tab." -ForegroundColor DarkGray -try { - ollama pull $ChatModel | Out-Host - Write-OK "$ChatModel ready (default chat model)" -} catch { - Write-Warn "$ChatModel pull skipped/failed - pull it from the Models tab later." -} -try { - ollama pull $MemModel | Out-Host - Write-OK "$MemModel ready (memory curator)" -} catch { - Write-Warn "$MemModel pull skipped/failed - the memory service will fall back to the chat model." -} - -# Pin it as the default chat model. Runs from the repo root so the synapse -# package imports; only writes the 'model' setting in the shared DB. -Write-Step "Setting $ChatModel as the default model" -Push-Location $RepoRoot -& $VenvPy -c "from synapse.memory.store import store; from synapse.nexus_config import DEFAULT_CHAT_MODEL; store.update_settings({'model': DEFAULT_CHAT_MODEL})" -$seedOk = ($LASTEXITCODE -eq 0) -Pop-Location -if ($seedOk) { Write-OK "Default model set to $ChatModel" } -else { Write-Warn "Could not persist default model - pick it at the top of the chat instead." } - -# -- Make Ollama manual-start (NexusOS owns the lifecycle) ---------------------- -Write-Step "Setting Ollama to manual start" -# The Ollama desktop app autostarts a server at every login, and the elevated -# 'ollama pull' above leaves an elevated server the user-level app cannot stop - -# which makes the Start/Stop AI button get stuck. Remove the login autostart and -# stop the running server so NexusOS controls Ollama via its Start AI button. -try { - $ollamaAutostart = Join-Path ([Environment]::GetFolderPath("Startup")) "Ollama.lnk" - if (Test-Path $ollamaAutostart) { - Remove-Item $ollamaAutostart -Force - Write-OK "Removed Ollama login autostart" - } - foreach ($img in @("ollama app.exe", "ollama.exe")) { - taskkill /F /T /IM $img 2>$null | Out-Null - } - Write-OK "Ollama set to manual start" -} catch { - Write-Warn "Could not adjust Ollama autostart - you can still Start/Stop AI from the app." -} - # -- ncp command --------------------------------------------------------------- # management\ncp.cmd goes on the machine PATH rather than a `function ncp` in the # PowerShell profile. Three reasons the profile route kept biting: @@ -220,6 +162,72 @@ try { Write-Warn "Could not create desktop shortcut - launch with: powershell -File launch_nexus.ps1" } +# -- Pull models (best-effort, LAST on purpose) -------------------------------- +# This is the only multi-GB step, and the only one a user is invited to Ctrl+C. +# Ctrl+C in PowerShell 5.1 terminates the whole SCRIPT, not just the running +# native command - so with this block in the middle, skipping the download also +# skipped the PATH registration and the desktop shortcut, and left an install +# that looked finished but had no working `ncp`. Everything that makes NexusOS +# usable now runs before we get here; abort at this point and you lose only the +# models, which the Models tab can pull later. +# +# Which models ship is decided in ONE place - DEFAULT_CHAT_MODEL and +# DEFAULT_MEMORY_MODEL in synapse\nexus_config.py (rationale documented there). +# Read them instead of hardcoding, so the installer can never pull one model +# while the backend defaults to another. +Push-Location $RepoRoot +$ChatModel = (& $VenvPy -c "from synapse.nexus_config import DEFAULT_CHAT_MODEL as m; print(m)") +$MemModel = (& $VenvPy -c "from synapse.nexus_config import DEFAULT_MEMORY_MODEL as m; print(m)") +Pop-Location +if ($LASTEXITCODE -ne 0 -or -not $ChatModel -or -not $MemModel) { + Write-Fail "Could not read the default models from synapse\nexus_config.py - the venv install is broken" +} + +Write-Step "Pulling models ($ChatModel for chat, $MemModel for memory)" +Write-Host " Downloads a few GB; press Ctrl+C to skip and pull them later from the Models tab." -ForegroundColor DarkGray +# No pipe: 'ollama pull' draws a progress bar with cursor control, and piping it +# (to Out-Host or anything else) buffers the redraws - the download then shows no +# output for minutes and reads as a hang. Let it own the console. +# No try/catch either: a native command that exits non-zero does not throw, so +# the catch never fired and a failed pull was reported as success. +ollama pull $ChatModel +if ($LASTEXITCODE -eq 0) { Write-OK "$ChatModel ready (default chat model)" } +else { Write-Warn "$ChatModel pull skipped/failed - pull it from the Models tab later." } + +ollama pull $MemModel +if ($LASTEXITCODE -eq 0) { Write-OK "$MemModel ready (memory curator)" } +else { Write-Warn "$MemModel pull skipped/failed - the memory service will fall back to the chat model." } + +# Pin it as the default chat model. Runs from the repo root so the synapse +# package imports; only writes the 'model' setting in the shared DB. +Write-Step "Setting $ChatModel as the default model" +Push-Location $RepoRoot +& $VenvPy -c "from synapse.memory.store import store; from synapse.nexus_config import DEFAULT_CHAT_MODEL; store.update_settings({'model': DEFAULT_CHAT_MODEL})" +$seedOk = ($LASTEXITCODE -eq 0) +Pop-Location +if ($seedOk) { Write-OK "Default model set to $ChatModel" } +else { Write-Warn "Could not persist default model - pick it at the top of the chat instead." } + +# -- Make Ollama manual-start (NexusOS owns the lifecycle) ---------------------- +Write-Step "Setting Ollama to manual start" +# The Ollama desktop app autostarts a server at every login, and the elevated +# 'ollama pull' above leaves an elevated server the user-level app cannot stop - +# which makes the Start/Stop AI button get stuck. Remove the login autostart and +# stop the running server so NexusOS controls Ollama via its Start AI button. +try { + $ollamaAutostart = Join-Path ([Environment]::GetFolderPath("Startup")) "Ollama.lnk" + if (Test-Path $ollamaAutostart) { + Remove-Item $ollamaAutostart -Force + Write-OK "Removed Ollama login autostart" + } + foreach ($img in @("ollama app.exe", "ollama.exe")) { + taskkill /F /T /IM $img 2>$null | Out-Null + } + Write-OK "Ollama set to manual start" +} catch { + Write-Warn "Could not adjust Ollama autostart - you can still Start/Stop AI from the app." +} + # -- Done ---------------------------------------------------------------------- Write-Host "" Write-Host " ==========================================================" -ForegroundColor Green @@ -231,8 +239,8 @@ Write-Host "" Write-Host " The app opens at http://localhost:8000" -ForegroundColor White Write-Host " The AI starts OFF - click 'Start AI' in the sidebar to turn it on." -ForegroundColor White Write-Host "" -Write-Host " Terminal: open a NEW PowerShell window, then run ncp help" -ForegroundColor White -Write-Host " (the profile that defines ncp is only read at startup)" -ForegroundColor DarkGray +Write-Host " Terminal: open a NEW terminal (PowerShell or cmd), then run ncp help" -ForegroundColor White +Write-Host " (PATH is read at process start, so open windows lack it)" -ForegroundColor DarkGray Write-Host " ==========================================================" -ForegroundColor Green Write-Host "" Read-Host "Press Enter to close" diff --git a/tests/test_smoke.py b/tests/test_smoke.py index 5ae38e8..12c817c 100644 --- a/tests/test_smoke.py +++ b/tests/test_smoke.py @@ -86,6 +86,16 @@ def test_ncp_is_registered_on_path_not_in_a_shell_profile(): assert "setx" not in code.lower() assert 'SetEnvironmentVariable("Path"' in code + # The model pull is the only multi-GB step and the only one the script + # invites a Ctrl+C on -- which in PS 5.1 kills the whole script. Anything + # after it is lost, so it has to come last. It used to sit in the middle, + # and skipping the download silently skipped the ncp registration too. + assert ps1.index("ollama pull") > ps1.index("Registering the ncp command"), \ + "model pull must come after ncp registration - a Ctrl+C there aborts the installer" + assert ps1.index("ollama pull") > ps1.index("Creating desktop shortcut"), \ + "model pull must come after the desktop shortcut" + + def test_first_playbook_is_the_system_prompt(tmp_path, monkeypatch): store = PlaybookFileStore(tmp_path)