From 9eda4ab077a1853ed036f8e8f4ae7b166da5dc33 Mon Sep 17 00:00:00 2001 From: xuelin-cell Date: Fri, 20 Mar 2026 18:54:31 +0800 Subject: [PATCH 01/28] fix(demo): stabilize Electron Windows packaging and build flow Use the backend uv environment for PyInstaller, enforce the correct packaging working directory, and add China mirror envs for Electron/electron-builder downloads to avoid startup and packaging failures. Made-with: Cursor --- .../openagent_demo/electron/package-lock.json | 4 +- libs/openagent_demo/electron/package.json | 3 +- .../electron/scripts/build-all.ps1 | 47 ++++++++++++++++ .../electron/scripts/build-backend.ps1 | 9 +-- libs/openagent_demo/frontend/eslint.config.js | 28 ++++++++++ .../openagent_demo/frontend/package-lock.json | 55 ++++++++++++++----- libs/openagent_demo/frontend/package.json | 3 + 7 files changed, 128 insertions(+), 21 deletions(-) create mode 100644 libs/openagent_demo/electron/scripts/build-all.ps1 create mode 100644 libs/openagent_demo/frontend/eslint.config.js diff --git a/libs/openagent_demo/electron/package-lock.json b/libs/openagent_demo/electron/package-lock.json index 2c14eb11..86887901 100644 --- a/libs/openagent_demo/electron/package-lock.json +++ b/libs/openagent_demo/electron/package-lock.json @@ -1,12 +1,12 @@ { "name": "openagent", - "version": "0.1.0", + "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openagent", - "version": "0.1.0", + "version": "0.0.1", "dependencies": { "tree-kill": "^1.2.2" }, diff --git a/libs/openagent_demo/electron/package.json b/libs/openagent_demo/electron/package.json index ea5e8d62..af28a707 100644 --- a/libs/openagent_demo/electron/package.json +++ b/libs/openagent_demo/electron/package.json @@ -66,7 +66,8 @@ "arch": ["x64"] } ], - "icon": "build/icon.ico" + "icon": "build/icon.ico", + "signAndEditExecutable": false }, "nsis": { "oneClick": false, diff --git a/libs/openagent_demo/electron/scripts/build-all.ps1 b/libs/openagent_demo/electron/scripts/build-all.ps1 new file mode 100644 index 00000000..2fd33da8 --- /dev/null +++ b/libs/openagent_demo/electron/scripts/build-all.ps1 @@ -0,0 +1,47 @@ +$ErrorActionPreference = 'Stop' + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ElectronDir = Resolve-Path "$ScriptDir\.." +$Target = if ($args.Count -gt 0) { $args[0] } else { 'win' } + +Write-Host '=========================================' +Write-Host ' OpenAgent Desktop - Build ('$Target')' +Write-Host '=========================================' +Write-Host '' + +Write-Host '[1/3] Building frontend...' +# Check if npm command exists +if (-not (Get-Command npm -ErrorAction SilentlyContinue)) { + Write-Error "npm command not found, please make sure Node.js is installed" +} + +# Build frontend +Set-Location "$ElectronDir\..\frontend" +npm install +npm run build + +Write-Host '' +Write-Host '[2/3] Skipping electron dependencies (already installed)...' +Set-Location $ElectronDir + +Write-Host '' +Write-Host '[2.5/3] Building backend...' +# Call PowerShell version of backend build script +& "$ScriptDir\build-backend.ps1" +Write-Host 'Backend build completed successfully!' +Set-Location $ElectronDir + +Write-Host '' +Write-Host '[3/3] Packaging Windows x64 installer...' +$env:ELECTRON_MIRROR = 'https://npmmirror.com/mirrors/electron/' +$env:ELECTRON_BUILDER_BINARIES_MIRROR = 'https://npmmirror.com/mirrors/electron-builder-binaries/' +npx electron-builder --win --x64 --publish never +Write-Host 'Electron packaging completed successfully!' + +Write-Host '' +Write-Host '=========================================' +Write-Host ' Build complete! Output in dist/' +Write-Host '=========================================' + +# List build artifacts +Get-ChildItem "$ElectronDir\dist\*.exe", "$ElectronDir\dist\*.blockmap" | Format-Table -AutoSize \ No newline at end of file diff --git a/libs/openagent_demo/electron/scripts/build-backend.ps1 b/libs/openagent_demo/electron/scripts/build-backend.ps1 index bc5da703..c4e64c03 100644 --- a/libs/openagent_demo/electron/scripts/build-backend.ps1 +++ b/libs/openagent_demo/electron/scripts/build-backend.ps1 @@ -5,12 +5,11 @@ $ElectronDir = Resolve-Path "$ScriptDir\.." $BackendDir = Resolve-Path "$ElectronDir\..\backend" Write-Host "==> Installing PyInstaller..." -pip install pyinstaller - -Write-Host "==> Building backend with PyInstaller..." Set-Location $BackendDir +uv pip install pyinstaller -pyinstaller ` +Write-Host "==> Building backend with PyInstaller..." +uv run pyinstaller ` --name openagent_api_server ` --onedir ` --noconfirm ` @@ -31,6 +30,8 @@ pyinstaller ` --hidden-import uvicorn.lifespan.on ` --hidden-import uvicorn.lifespan.off ` --collect-submodules openagent_api ` + --collect-submodules openagent ` + --collect-data openagent ` --add-data "skills;skills" ` openagent_api/server.py diff --git a/libs/openagent_demo/frontend/eslint.config.js b/libs/openagent_demo/frontend/eslint.config.js new file mode 100644 index 00000000..e05f7651 --- /dev/null +++ b/libs/openagent_demo/frontend/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist', 'build'] }, + js.configs.recommended, + ...tseslint.configs.recommended, + { + languageOptions: { + ecmaVersion: 2022, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) diff --git a/libs/openagent_demo/frontend/package-lock.json b/libs/openagent_demo/frontend/package-lock.json index f587e473..872c9609 100644 --- a/libs/openagent_demo/frontend/package-lock.json +++ b/libs/openagent_demo/frontend/package-lock.json @@ -1,17 +1,18 @@ { "name": "openagent-demo-frontend", - "version": "0.1.0", + "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openagent-demo-frontend", - "version": "0.1.0", + "version": "0.0.1", "dependencies": { "docx-preview": "^0.3.7", "echarts": "^6.0.0", "lucide-react": "^0.577.0", "mermaid": "^11.13.0", + "pdfjs-dist": "^5.4.296", "pptx-viewer": "^0.1.0", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -26,6 +27,8 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/echarts": "^4.9.22", + "@types/mermaid": "^9.1.0", "@types/react": "^19.0.2", "@types/react-dom": "^19.0.2", "@types/react-syntax-highlighter": "^15.5.13", @@ -2029,6 +2032,16 @@ "@types/ms": "*" } }, + "node_modules/@types/echarts": { + "version": "4.9.22", + "resolved": "https://registry.npmjs.org/@types/echarts/-/echarts-4.9.22.tgz", + "integrity": "sha512-7Fo6XdWpoi8jxkwP7BARUOM7riq8bMhmsCtSG8gzUcJmFhLo387tihoBYS/y5j7jl3PENT5RxeWZdN9RiwO7HQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/zrender": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2075,6 +2088,13 @@ "@types/unist": "*" } }, + "node_modules/@types/mermaid": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@types/mermaid/-/mermaid-9.1.0.tgz", + "integrity": "sha512-rc8QqhveKAY7PouzY/p8ljS+eBSNCv7o79L97RSub/Ic2SQ34ph1Ng3s8wFLWVjvaEt6RLOWtSCsgYWd95NY8A==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -2129,6 +2149,13 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, + "node_modules/@types/zrender": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/zrender/-/zrender-4.0.6.tgz", + "integrity": "sha512-1jZ9bJn2BsfmYFPBHtl5o3uV+ILejAtGrDcYSpT4qaVKEI/0YY+arw3XHU04Ebd8Nca3SQ7uNcLaqiL+tTFVMg==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.56.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", @@ -5678,6 +5705,18 @@ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "license": "MIT" }, + "node_modules/pdfjs-dist": { + "version": "5.4.296", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.296.tgz", + "integrity": "sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=20.16.0 || >=22.3.0" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.80" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5885,18 +5924,6 @@ } } }, - "node_modules/react-pdf/node_modules/pdfjs-dist": { - "version": "5.4.296", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.296.tgz", - "integrity": "sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==", - "license": "Apache-2.0", - "engines": { - "node": ">=20.16.0 || >=22.3.0" - }, - "optionalDependencies": { - "@napi-rs/canvas": "^0.1.80" - } - }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", diff --git a/libs/openagent_demo/frontend/package.json b/libs/openagent_demo/frontend/package.json index c14a0bc8..0a735da4 100644 --- a/libs/openagent_demo/frontend/package.json +++ b/libs/openagent_demo/frontend/package.json @@ -14,6 +14,7 @@ "echarts": "^6.0.0", "lucide-react": "^0.577.0", "mermaid": "^11.13.0", + "pdfjs-dist": "^5.4.296", "pptx-viewer": "^0.1.0", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -28,6 +29,8 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/echarts": "^4.9.22", + "@types/mermaid": "^9.1.0", "@types/react": "^19.0.2", "@types/react-dom": "^19.0.2", "@types/react-syntax-highlighter": "^15.5.13", From f3f358dec9c61dcb8b5229341dbd5f9bb9e8ab21 Mon Sep 17 00:00:00 2001 From: aone Date: Tue, 24 Mar 2026 10:24:44 +0800 Subject: [PATCH 02/28] mod: windows wsl and vm install GUI and fix bind user folder. --- .../openagent/computer/local/_wsl.py | 40 +- .../openagent/computer/local/vm_win.py | 8 +- .../openagent/harness/environment.py | 16 +- .../tests/unit_tests/computer/test_vm.py | 1 - .../tests/unit_tests/computer/test_wsl.py | 25 +- .../unit_tests/harness/test_environment.py | 12 +- .../backend/openagent_api/agent_manager.py | 66 +++- .../backend/openagent_api/routes/setup.py | 342 +++++++++++++++++- .../openagent_demo/electron/package-lock.json | 64 ++-- libs/openagent_demo/electron/package.json | 11 +- .../frontend/src/components/SettingsModal.tsx | 41 ++- 11 files changed, 539 insertions(+), 87 deletions(-) diff --git a/libs/openagent/openagent/computer/local/_wsl.py b/libs/openagent/openagent/computer/local/_wsl.py index 8ed05e10..5724ac0d 100644 --- a/libs/openagent/openagent/computer/local/_wsl.py +++ b/libs/openagent/openagent/computer/local/_wsl.py @@ -19,6 +19,7 @@ import asyncio import contextlib import json +import logging import os import re import shlex @@ -32,6 +33,8 @@ from openagent.exceptions import MissingDependencyError, UnsupportedPlatformError, WslError from openagent.types import CLIResult +logger = logging.getLogger(__name__) + # UNC path prefixes for accessing WSL filesystem from Windows. # Modern Windows 11 uses ``wsl.localhost``; older builds use ``wsl$``. _UNC_PREFIXES = (r"\\wsl.localhost", r"\\wsl$") @@ -199,12 +202,11 @@ def write_mounts(self, mounts: list[ResolvedMount]) -> None: The change takes effect on the next distro restart (via ``apply_mounts`` or ``start``). - Raises: - WslError: If the config directory does not exist. + Creates the config directory if missing. This can happen when a distro + was installed outside ``build()`` (for example via setup migration or + manual WSL import) and cowork mounts are configured later. """ - if not self._config_dir.exists(): - msg = f"Config directory not found for instance '{self._instance}'" - raise WslError(msg) + self._config_dir.mkdir(parents=True, exist_ok=True) entries = [ { @@ -489,6 +491,8 @@ async def _apply_bind_mounts(self) -> None: if not mounts: return + logger.debug("WSL applying %d bind mount(s) from mounts.json", len(mounts)) + for m in mounts: wsl_host = _win_path_to_wsl(m.host_path) qguest = shlex.quote(m.guest_path) @@ -497,6 +501,12 @@ async def _apply_bind_mounts(self) -> None: # Skip if already mounted. check = await self.shell(f"mountpoint -q {qguest}", user="root") if check.exit_code == 0: + logger.info( + "WSL bind-mount already active: guest=%s wsl_source=%s host_win=%s", + m.guest_path, + wsl_host, + m.host_path, + ) continue cmd = f"mkdir -p {qguest} && mount --bind {qhost} {qguest}" @@ -508,6 +518,26 @@ async def _apply_bind_mounts(self) -> None: msg = f"Failed to bind-mount {m.host_path} → {m.guest_path}: {result.stderr}" raise WslError(msg) + # Post-verify so logs show whether the guest path is really a mount (debug empty ls issues). + verify = await self.shell(f"findmnt -n {qguest}", user="root") + if verify.exit_code == 0 and (verify.stdout or "").strip(): + logger.info( + "WSL bind-mount applied+verified: guest=%s host_win=%s findmnt=%s", + m.guest_path, + m.host_path, + verify.stdout.strip().replace("\n", " | "), + ) + else: + logger.warning( + "WSL bind-mount mount(8) succeeded but findmnt could not confirm guest=%s " + "(host_win=%s, wsl_source=%s, findmnt_exit=%s, findmnt_stderr=%s)", + m.guest_path, + m.host_path, + wsl_host, + verify.exit_code, + (verify.stderr or "").strip() or "(empty)", + ) + async def _resolve_unc_prefix(self) -> str: r"""Resolve and cache the working UNC prefix for this system. diff --git a/libs/openagent/openagent/computer/local/vm_win.py b/libs/openagent/openagent/computer/local/vm_win.py index 670af4bc..5dce2468 100644 --- a/libs/openagent/openagent/computer/local/vm_win.py +++ b/libs/openagent/openagent/computer/local/vm_win.py @@ -73,11 +73,12 @@ class _VMSessionComputer(AsyncComputerMixin): session_name: Linux username for this session. """ - def __init__(self, *, vm: WslVM, session_name: str) -> None: + def __init__(self, *, vm: WslVM, session_name: str, default_cwd: str | None = None) -> None: """Initialize with a VM backend and session name.""" self._vm = vm self._session_name = session_name self._active = True + self._default_cwd = default_cwd @property def session_name(self) -> str: @@ -89,6 +90,10 @@ def is_running(self) -> bool: """True if this handle is active.""" return self._active + def set_default_cwd(self, cwd: str | None) -> None: + """Set the default working directory for future commands.""" + self._default_cwd = cwd + async def start(self) -> None: """Health check — verify the distro is running and session user exists. @@ -131,6 +136,7 @@ async def run( return await self._vm.shell( command, user=self._session_name, + cwd=self._default_cwd, timeout=timeout_ms / 1000, ) except VMError as e: diff --git a/libs/openagent/openagent/harness/environment.py b/libs/openagent/openagent/harness/environment.py index 56ef438b..c5724e41 100644 --- a/libs/openagent/openagent/harness/environment.py +++ b/libs/openagent/openagent/harness/environment.py @@ -72,13 +72,15 @@ async def resolve(self) -> EnvironmentContext: # Parse into a timezone-aware datetime. # Shell outputs ISO 8601 with numeric offset, e.g. "2026-02-14T10:30:00-0800". raw_dt = values[5] - if not raw_dt: - msg = f"Environment shell returned empty datetime (raw output: {result.stdout!r})" - raise ValueError(msg) - try: - now = datetime.strptime(raw_dt, "%Y-%m-%dT%H:%M:%S%z") - except ValueError: - now = datetime.strptime(raw_dt[:19], "%Y-%m-%dT%H:%M:%S") # noqa: DTZ007 + if raw_dt: + try: + now = datetime.strptime(raw_dt, "%Y-%m-%dT%H:%M:%S%z") + except ValueError: + now = datetime.strptime(raw_dt[:19], "%Y-%m-%dT%H:%M:%S") # noqa: DTZ007 + else: + # Some hosts occasionally return empty stdout for this probe. + # Degrade gracefully so environment detection does not block task execution. + now = datetime.now().astimezone() return EnvironmentContext( working_dir=values[0], diff --git a/libs/openagent/tests/unit_tests/computer/test_vm.py b/libs/openagent/tests/unit_tests/computer/test_vm.py index 516e9d1c..b73fab91 100644 --- a/libs/openagent/tests/unit_tests/computer/test_vm.py +++ b/libs/openagent/tests/unit_tests/computer/test_vm.py @@ -195,7 +195,6 @@ async def test_run_translates_vm_error_to_cli_error(self) -> None: with pytest.raises(CLIError, match="boom"): await computer.run("bad") - class TestUpload: """Tests for upload().""" diff --git a/libs/openagent/tests/unit_tests/computer/test_wsl.py b/libs/openagent/tests/unit_tests/computer/test_wsl.py index 9ef9e67c..0479f56e 100644 --- a/libs/openagent/tests/unit_tests/computer/test_wsl.py +++ b/libs/openagent/tests/unit_tests/computer/test_wsl.py @@ -179,6 +179,25 @@ async def test_run_translates_vm_error_to_cli_error(self) -> None: with pytest.raises(CLIError, match="boom"): await computer.run("bad") + async def test_run_passes_default_cwd_to_vm_shell(self) -> None: + vm = _mock_vm() + computer = _VMSessionComputer(vm=vm, session_name="test-session", default_cwd="/sessions/test-session/mnt/code") + + await computer.run("pwd") + + call_kwargs = vm.shell.call_args.kwargs + assert call_kwargs["cwd"] == "/sessions/test-session/mnt/code" + + async def test_set_default_cwd_updates_future_run_calls(self) -> None: + vm = _mock_vm() + computer = _make_computer(vm) + computer.set_default_cwd("/sessions/test-session/mnt/project") + + await computer.run("pwd") + + call_kwargs = vm.shell.call_args.kwargs + assert call_kwargs["cwd"] == "/sessions/test-session/mnt/project" + class TestUpload: """Tests for upload().""" @@ -626,20 +645,22 @@ async def test_applies_bind_mounts(self) -> None: mock_shell.side_effect = [ _fail(), # mountpoint -q /mnt/code -> not mounted _ok(), # mount --bind ... /mnt/code + _ok(stdout="/mnt/c/Users/foo/code"), # findmnt -n /mnt/code _fail(), # mountpoint -q /mnt/data -> not mounted _ok(), # mount --bind ... /mnt/data (+ remount ro) + _ok(stdout="/mnt/d/data"), # findmnt -n /mnt/data ] await vm._apply_bind_mounts() - assert mock_shell.await_count == 4 + assert mock_shell.await_count == 6 # Check writable mount (no remount) mount_call_1 = mock_shell.call_args_list[1].args[0] assert "mount --bind" in mount_call_1 assert "/mnt/c/Users/foo/code" in mount_call_1 assert "remount,ro" not in mount_call_1 # Check read-only mount (remount ro) - mount_call_2 = mock_shell.call_args_list[3].args[0] + mount_call_2 = mock_shell.call_args_list[4].args[0] assert "mount --bind" in mount_call_2 assert "remount,ro" in mount_call_2 diff --git a/libs/openagent/tests/unit_tests/harness/test_environment.py b/libs/openagent/tests/unit_tests/harness/test_environment.py index d17a3da1..db9da23a 100644 --- a/libs/openagent/tests/unit_tests/harness/test_environment.py +++ b/libs/openagent/tests/unit_tests/harness/test_environment.py @@ -83,10 +83,10 @@ async def test_datetime_without_timezone_fallback(self) -> None: assert env.today_date.year == 2026 assert env.today_date.tzinfo is None - async def test_empty_datetime_raises(self) -> None: + async def test_empty_datetime_fallback_now(self) -> None: computer = _mock_computer(_make_stdout(date="")) - with pytest.raises(ValueError, match="empty datetime"): - await EnvironmentResolver(computer).resolve() + env = await EnvironmentResolver(computer).resolve() + assert isinstance(env.today_date, datetime) async def test_pads_missing_parts(self) -> None: """When stdout has fewer delimiters, missing fields are padded.""" @@ -94,9 +94,9 @@ async def test_pads_missing_parts(self) -> None: stdout = f"/home/user\n{_DELIM}\ntrue" computer = _mock_computer(stdout) - # Date will be empty → raises ValueError - with pytest.raises(ValueError, match="empty datetime"): - await EnvironmentResolver(computer).resolve() + # Date will be empty -> fallback to current time. + env = await EnvironmentResolver(computer).resolve() + assert isinstance(env.today_date, datetime) async def test_darwin_platform(self) -> None: computer = _mock_computer(_make_stdout(platform="darwin", os_version="Darwin 25.3.0")) diff --git a/libs/openagent_demo/backend/openagent_api/agent_manager.py b/libs/openagent_demo/backend/openagent_api/agent_manager.py index 9786791d..f2ba3733 100644 --- a/libs/openagent_demo/backend/openagent_api/agent_manager.py +++ b/libs/openagent_demo/backend/openagent_api/agent_manager.py @@ -20,6 +20,7 @@ import json import logging import os +import sys from typing import Any from collections.abc import AsyncIterator @@ -48,6 +49,8 @@ def __init__(self) -> None: self._agent_locks: dict[tuple[str, str], asyncio.Lock] = {} # session_name -> (working_dir_source, mount_target) self._session_working_dirs: dict[str, tuple[str, str]] = {} + # Background warm-up task: auto-start VM manager after app startup. + self._vm_warm_task: asyncio.Task[None] | None = None def conversation_lock(self, conversation_id: str) -> asyncio.Lock: """Per-conversation lock to serialise prepare/send/mount operations.""" @@ -55,6 +58,13 @@ def conversation_lock(self, conversation_id: str) -> asyncio.Lock: self._conv_locks[conversation_id] = asyncio.Lock() return self._conv_locks[conversation_id] + @staticmethod + def _set_computer_default_cwd(computer: Any, cwd: str | None) -> None: + """Best-effort hook for session computers that support default cwd.""" + setter = getattr(computer, "set_default_cwd", None) + if callable(setter): + setter(cwd) + # ── Computer management ── async def _ensure_computer( @@ -111,10 +121,15 @@ async def _ensure_computer( if session_name and session_name in self._computers: return self._computers[session_name], session_name - # Guard: check that limactl is available before attempting VM ops + # Guard: check that the platform VM backend is available before VM ops import shutil - if not shutil.which("limactl"): + vm_backend_ready = ( + bool(shutil.which("wsl.exe") or shutil.which("wsl")) + if sys.platform == "win32" + else bool(shutil.which("limactl")) + ) + if not vm_backend_ready: raise RuntimeError( "Cowork mode requires VM setup. " "Please install and configure it in Settings \u2192 Sandbox." @@ -134,10 +149,18 @@ async def _ensure_computer( from openagent.computer import Mount session_mounts: list[Mount] | None = None + default_cwd: str | None = None if working_dir: - session_mounts = [Mount(source=working_dir, target=Path(working_dir).name, writable=True)] + mount_target = Path(working_dir).name + session_mounts = [Mount(source=working_dir, target=mount_target, writable=True)] + default_cwd = f"/sessions/{{session}}/mnt/{mount_target}" logger.info("Creating new session (mounts=%s)...", session_mounts) computer = await self._vm_manager.computer(mounts=session_mounts) + if default_cwd is not None: + self._set_computer_default_cwd( + computer, + default_cwd.format(session=computer.session_name), + ) except FileNotFoundError: raise RuntimeError( "Cowork mode requires VM setup. " @@ -392,16 +415,38 @@ async def start(self) -> None: from dotenv import load_dotenv load_dotenv() - # VM setup is lazy — _ensure_vm_manager() is called on demand - # when a cowork-mode conversation starts. + # Keep startup fast: warm VM in background (non-blocking). Cowork + # still lazily initializes on first use if warm-up fails. + self._schedule_vm_warmup() + logger.info("Agent manager initialized.") + + def _schedule_vm_warmup(self) -> None: + """Best-effort background VM warm-up on supported hosts.""" + if self._vm_warm_task is not None and not self._vm_warm_task.done(): + return + + import shutil + + vm_backend_available = ( + bool(shutil.which("wsl.exe") or shutil.which("wsl")) + if sys.platform == "win32" + else bool(shutil.which("limactl")) + ) + if not vm_backend_available: + return + + self._vm_warm_task = asyncio.create_task(self._warm_vm_manager()) + + async def _warm_vm_manager(self) -> None: + """Background VM initialization used to auto-start installed VMs.""" try: await self._ensure_vm_manager() + logger.info("Background VM warm-up completed.") except Exception: logger.warning( - "VM manager not available (Lima not installed?). " - "Cowork mode will be unavailable; chat mode still works." + "Background VM warm-up failed; cowork mode will initialize VM on demand.", + exc_info=True, ) - logger.info("Agent manager initialized.") async def ensure_agent( self, @@ -451,6 +496,9 @@ async def stream_response( async def stop(self) -> None: """Shut down all agents and computers.""" + if self._vm_warm_task is not None and not self._vm_warm_task.done(): + self._vm_warm_task.cancel() + self._vm_warm_task = None for key, agent in self._agents.items(): logger.info("Closing agent for %s...", key) await agent.aclose() @@ -526,6 +574,8 @@ async def mount_working_dir(self, session_name: str, working_dir: str) -> None: mount = Mount(source=working_dir, target=new_target, writable=True) await self._vm_manager.mount([mount], session=session_name) self._session_working_dirs[session_name] = (working_dir, new_target) + computer = self._computers.get(session_name) + self._set_computer_default_cwd(computer, f"/sessions/{session_name}/mnt/{new_target}") logger.info("Mounted working dir %s for session %s", working_dir, session_name) # Remove the stale mount-point directory left behind after unmount. diff --git a/libs/openagent_demo/backend/openagent_api/routes/setup.py b/libs/openagent_demo/backend/openagent_api/routes/setup.py index 78c8f648..9841ee2f 100644 --- a/libs/openagent_demo/backend/openagent_api/routes/setup.py +++ b/libs/openagent_demo/backend/openagent_api/routes/setup.py @@ -25,7 +25,7 @@ from fastapi import APIRouter, HTTPException from fastapi.responses import Response, StreamingResponse -from openagent_api.paths import deps_dir, vm_lima_dir, vm_setup_dir +from openagent_api.paths import data_dir, deps_dir, vm_lima_dir, vm_setup_dir logger = logging.getLogger(__name__) @@ -156,6 +156,107 @@ def _lima_status() -> dict[str, object]: return {"installed": False, "path": None, "managed": False} +# --------------------------------------------------------------------------- +# WSL (Windows) +# --------------------------------------------------------------------------- + +_WSL_INSTANCE = "openagent" +_WSL_EXPORT_SOURCE = "Ubuntu" + + +def _wsl_cmd() -> str | None: + return shutil.which("wsl.exe") or shutil.which("wsl") + + +def _decode_wsl_output(raw: bytes) -> str: + if raw[:2] == b"\xff\xfe" or b"\x00" in raw: + return raw.decode("utf-16-le", errors="replace").replace("\x00", "") + return raw.decode("utf-8", errors="replace") + + +def _parse_wsl_list(text: str) -> list[dict[str, str]]: + entries: list[dict[str, str]] = [] + for line in text.splitlines(): + stripped = line.strip() + if not stripped: + continue + if "NAME" in stripped.upper() and "STATE" in stripped.upper(): + continue + if stripped.startswith("*"): + stripped = stripped[1:].strip() + parts = stripped.split() + if len(parts) < 3: + continue + entries.append({"name": parts[0], "state": parts[1], "version": parts[2]}) + return entries + + +async def _wsl_list() -> list[dict[str, str]]: + if not _wsl_cmd(): + return [] + proc = await asyncio.create_subprocess_exec( + "wsl.exe", + "--list", + "--verbose", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + out_b, _ = await proc.communicate() + if proc.returncode != 0: + return [] + return _parse_wsl_list(_decode_wsl_output(out_b or b"")) + + +async def _wsl_instance_status() -> str | None: + entries = await _wsl_list() + for entry in entries: + if entry["name"].lower() == _WSL_INSTANCE.lower(): + return entry["state"] + return None + + +def _wsl_status() -> dict[str, object]: + wsl = _wsl_cmd() + return {"installed": bool(wsl), "path": wsl, "managed": False} + + +def _win_path_to_wsl(path: Path | str) -> str: + s = str(path).replace("\\", "/") + m = re.match(r"^([A-Za-z]):(.*)$", s) + if not m: + raise ValueError(f"Unsupported Windows path for WSL conversion: {s}") + drive = m.group(1).lower() + rest = m.group(2) + if not rest.startswith("/"): + rest = "/" + rest + return f"/mnt/{drive}{rest}" + + +async def _wsl_shell(cmd: str, *, timeout: float = 60) -> tuple[int, str, str]: + proc = await asyncio.create_subprocess_exec( + "wsl.exe", + "-d", + _WSL_INSTANCE, + "--", + "bash", + "-lc", + cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + try: + stdout_b, stderr_b = await asyncio.wait_for(proc.communicate(), timeout=timeout) + except asyncio.TimeoutError: + proc.kill() + await proc.wait() + return 1, "", "Timed out" + return ( + proc.returncode or 0, + (stdout_b or b"").decode("utf-8", errors="replace"), + (stderr_b or b"").decode("utf-8", errors="replace"), + ) + + async def _install_lima_stream(): """SSE generator that downloads and installs Lima.""" def sse(event: str, data: dict[str, object]) -> str: @@ -219,6 +320,35 @@ def _extract() -> None: shutil.rmtree(tmp_dir, ignore_errors=True) +async def _install_wsl_stream(): + """SSE generator that enables WSL on Windows.""" + def sse(event: str, data: dict[str, object]) -> str: + return f"event: {event}\ndata: {json.dumps(data)}\n\n" + + wsl = _wsl_cmd() + if wsl: + yield sse("done", {"message": f"WSL already installed ({wsl})"}) + return + + yield sse("progress", {"step": "installing", "message": "Installing WSL components..."}) + proc = await asyncio.create_subprocess_exec( + "wsl.exe", + "--install", + "--no-distribution", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + out_b, err_b = await proc.communicate() + out = _decode_wsl_output(out_b or b"").strip() + err = _decode_wsl_output(err_b or b"").strip() + if proc.returncode != 0: + msg = err or out or "Failed to install WSL. Please run 'wsl --install' as Administrator." + yield sse("error", {"message": msg}) + return + + yield sse("done", {"message": "WSL installed. A reboot may be required before continuing."}) + + # --------------------------------------------------------------------------- # Platform dispatch # --------------------------------------------------------------------------- @@ -247,8 +377,7 @@ def _vm_status() -> dict[str, object]: if sys.platform == "darwin": return {"supported": True, "backend": "lima", **_lima_status()} if sys.platform == "win32": - # Placeholder for WSL support - return {"supported": True, "backend": "wsl", "installed": False} + return {"supported": True, "backend": "wsl", **_wsl_status()} return {"supported": False, "backend": None, "installed": False, "reason": f"No VM backend for {sys.platform}"} @@ -272,7 +401,10 @@ async def get_vm_status() -> dict[str, object]: instance_status: str | None = None if result.get("installed"): try: - instance_status = await _lima_instance_status() + if result.get("backend") == "lima": + instance_status = await _lima_instance_status() + elif result.get("backend") == "wsl": + instance_status = await _wsl_instance_status() vm_ready = instance_status == "Running" except Exception: pass @@ -298,8 +430,9 @@ async def install_vm_backend() -> StreamingResponse: backend = status["backend"] if backend == "lima": return StreamingResponse(_install_lima_stream(), media_type="text/event-stream") + if backend == "wsl": + return StreamingResponse(_install_wsl_stream(), media_type="text/event-stream") - # Future: WSL installation raise HTTPException(status_code=501, detail=f"Auto-install not yet supported for {backend}") @@ -450,6 +583,12 @@ class _BuildManager(_ProcessManager): """Manages ``limactl start`` to create or boot the VM.""" async def _run(self, **kwargs: object) -> None: + if sys.platform == "win32": + await self._run_wsl() + return + await self._run_lima() + + async def _run_lima(self) -> None: instance_status = await _lima_instance_status() if instance_status == "Running": @@ -501,6 +640,114 @@ async def _run(self, **kwargs: object) -> None: self._status = "error" self._error = f"exit {proc.returncode}" + async def _run_wsl(self) -> None: + if not _wsl_cmd(): + self._emit("error", {"message": "WSL is not installed. Install it first in Phase 1."}) + self._status = "error" + self._error = "WSL missing" + return + + status = await _wsl_instance_status() + if status == "Running": + self._emit("done", {"message": "WSL distro is already running"}) + self._status = "done" + return + + if status == "Stopped": + self._emit("progress", {"step": "starting", "message": "Starting existing WSL distro..."}) + proc = await asyncio.create_subprocess_exec( + "wsl.exe", "-d", _WSL_INSTANCE, "--", "echo", "ok", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + self._process = proc + _, stderr_b = await proc.communicate() + if proc.returncode == 0: + self._emit("done", {"message": "WSL distro started successfully"}) + self._status = "done" + else: + err = _decode_wsl_output(stderr_b or b"").strip() + self._emit("error", {"message": err or f"WSL start failed (exit {proc.returncode})"}) + self._status = "error" + self._error = f"exit {proc.returncode}" + return + + # Distro does not exist: bootstrap from Ubuntu export. + self._emit("progress", {"step": "creating", "message": "Preparing source distro (Ubuntu)..."}) + entries = await _wsl_list() + has_source = any(e["name"].lower() == _WSL_EXPORT_SOURCE.lower() for e in entries) + if not has_source: + proc = await asyncio.create_subprocess_exec( + "wsl.exe", "--install", "-d", _WSL_EXPORT_SOURCE, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + self._process = proc + out_b, err_b = await proc.communicate() + if proc.returncode != 0: + err = (_decode_wsl_output(err_b or b"") or _decode_wsl_output(out_b or b"")).strip() + self._emit( + "error", + {"message": err or "Failed to install Ubuntu distro. Try running `wsl --install -d Ubuntu` manually once."}, + ) + self._status = "error" + self._error = f"exit {proc.returncode}" + return + self._emit("progress", {"step": "creating", "message": "Ubuntu installed. Continuing..."}) + + export_root = deps_dir() / "wsl" + export_root.mkdir(parents=True, exist_ok=True) + export_tar = export_root / f"{_WSL_EXPORT_SOURCE.lower()}-seed.tar" + import_dir = data_dir() / "wsl" / _WSL_INSTANCE / "disk" + import_dir.mkdir(parents=True, exist_ok=True) + + self._emit("progress", {"step": "creating", "message": "Exporting Ubuntu rootfs..."}) + proc_export = await asyncio.create_subprocess_exec( + "wsl.exe", "--export", _WSL_EXPORT_SOURCE, str(export_tar), + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + self._process = proc_export + _, err_b = await proc_export.communicate() + if proc_export.returncode != 0: + err = _decode_wsl_output(err_b or b"").strip() + self._emit("error", {"message": err or f"WSL export failed (exit {proc_export.returncode})"}) + self._status = "error" + self._error = f"exit {proc_export.returncode}" + return + + self._emit("progress", {"step": "creating", "message": "Importing OpenAgent WSL distro..."}) + proc_import = await asyncio.create_subprocess_exec( + "wsl.exe", "--import", _WSL_INSTANCE, str(import_dir), str(export_tar), "--version", "2", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + self._process = proc_import + _, err_b = await proc_import.communicate() + if proc_import.returncode != 0: + err = _decode_wsl_output(err_b or b"").strip() + self._emit("error", {"message": err or f"WSL import failed (exit {proc_import.returncode})"}) + self._status = "error" + self._error = f"exit {proc_import.returncode}" + return + + self._emit("progress", {"step": "starting", "message": "Starting OpenAgent WSL distro..."}) + proc_start = await asyncio.create_subprocess_exec( + "wsl.exe", "-d", _WSL_INSTANCE, "--", "echo", "ok", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + self._process = proc_start + _, err_b = await proc_start.communicate() + if proc_start.returncode == 0: + self._emit("done", {"message": "WSL distro created and started successfully"}) + self._status = "done" + else: + err = _decode_wsl_output(err_b or b"").strip() + self._emit("error", {"message": err or f"WSL start failed (exit {proc_start.returncode})"}) + self._status = "error" + self._error = f"exit {proc_start.returncode}" + async def _stream_stderr(self, proc: asyncio.subprocess.Process) -> None: """Read limactl stderr line-by-line and emit progress events.""" assert proc.stderr is not None @@ -547,6 +794,12 @@ class _ProvisionManager(_ProcessManager): """Manages setup.sh execution inside the Lima VM.""" async def _run(self, **kwargs: object) -> None: + if sys.platform == "win32": + await self._run_wsl(**kwargs) + return + await self._run_lima(**kwargs) + + async def _run_lima(self, **kwargs: object) -> None: force = bool(kwargs.get("force", False)) # 1. Verify VM is running @@ -633,6 +886,65 @@ async def _run(self, **kwargs: object) -> None: self._status = "error" self._error = f"exit {proc.returncode}" + async def _run_wsl(self, **kwargs: object) -> None: + force = bool(kwargs.get("force", False)) + instance_status = await _wsl_instance_status() + if instance_status != "Running": + self._emit("error", {"message": f"WSL distro is not running (status: {instance_status})"}) + self._status = "error" + self._error = "WSL distro not running" + return + + self._emit("progress", {"step": "copying", "message": "Preparing setup files in WSL..."}) + setup_dir = vm_setup_dir() + if not setup_dir.is_dir(): + self._emit("error", {"message": f"Setup directory not found: {setup_dir}"}) + self._status = "error" + self._error = "Setup dir not found" + return + + setup_wsl = _win_path_to_wsl(setup_dir) + rc, _, err = await _wsl_shell( + f"sudo rm -rf {_SETUP_VM_DIR} && sudo mkdir -p {_SETUP_VM_DIR} && " + f"sudo cp -r {setup_wsl}/. {_SETUP_VM_DIR}/", + timeout=60, + ) + if rc != 0: + self._emit("error", {"message": f"Failed to stage setup files in WSL: {err}"}) + self._status = "error" + self._error = "Stage failed" + return + + self._emit("progress", {"step": "starting", "message": "Starting provisioning..."}) + cmd = f"sudo bash {_SETUP_VM_DIR}/setup.sh" + if force: + cmd += " --force" + + proc = await asyncio.create_subprocess_exec( + "wsl.exe", "-d", _WSL_INSTANCE, "--", "bash", "-lc", cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + self._process = proc + + assert proc.stdout is not None + while True: + line = await proc.stdout.readline() + if not line: + break + text = line.decode("utf-8", errors="replace").strip() + if text.startswith("@@SETUP:"): + self._handle_setup_line(text) + + await proc.wait() + if proc.returncode == 0: + self._emit("done", {"message": "Provisioning complete"}) + self._status = "done" + else: + self._emit("error", {"message": f"Provisioning failed (exit {proc.returncode})"}) + self._status = "error" + self._error = f"exit {proc.returncode}" + def _handle_setup_line(self, line: str) -> None: """Parse @@SETUP:step_id:status:message and emit typed SSE events.""" # Format: @@SETUP::: @@ -656,11 +968,16 @@ def _handle_setup_line(self, line: str) -> None: async def check_markers(self) -> dict[str, object]: """Read VM-side marker files to determine provision state.""" - instance_status = await _lima_instance_status() + if sys.platform == "win32": + instance_status = await _wsl_instance_status() + shell = _wsl_shell + else: + instance_status = await _lima_instance_status() + shell = _lima_shell if instance_status != "Running": return {"provisioned": False, "steps_done": [], "total_steps": len(_PROVISION_STEPS)} - rc, stdout, _ = await _lima_shell(f"ls {_SETUP_MARKER_DIR}/*.done 2>/dev/null || true") + rc, stdout, _ = await shell(f"ls {_SETUP_MARKER_DIR}/*.done 2>/dev/null || true") if rc != 0 or not stdout.strip(): return {"provisioned": False, "steps_done": [], "total_steps": len(_PROVISION_STEPS)} @@ -679,7 +996,8 @@ async def check_markers(self) -> dict[str, object]: async def get_log(self) -> str: """Fetch the latest setup log from the VM.""" - rc, stdout, _ = await _lima_shell( + shell = _wsl_shell if sys.platform == "win32" else _lima_shell + rc, stdout, _ = await shell( f"ls -t {_SETUP_LOG_DIR}/setup-*.log 2>/dev/null | head -1 | xargs cat 2>/dev/null | tail -500", timeout=15, ) @@ -718,7 +1036,8 @@ async def build_vm() -> StreamingResponse: """Create or start the VM. Streams SSE progress events.""" status = _vm_status() if not status.get("installed"): - raise HTTPException(status_code=422, detail="VM backend (Lima) is not installed") + backend = status.get("backend") or "vm backend" + raise HTTPException(status_code=422, detail=f"VM backend ({backend}) is not installed") mgr = _get_build_manager() if mgr._status != "running": @@ -732,7 +1051,10 @@ async def get_build_status() -> dict[str, object]: mgr = _get_build_manager() result = dict(mgr.status_dict()) if mgr._status in ("idle", "done", "error"): - result["vm_state"] = await _lima_instance_status() + if sys.platform == "win32": + result["vm_state"] = await _wsl_instance_status() + else: + result["vm_state"] = await _lima_instance_status() return result diff --git a/libs/openagent_demo/electron/package-lock.json b/libs/openagent_demo/electron/package-lock.json index 86887901..5000cb64 100644 --- a/libs/openagent_demo/electron/package-lock.json +++ b/libs/openagent_demo/electron/package-lock.json @@ -11,7 +11,7 @@ "tree-kill": "^1.2.2" }, "devDependencies": { - "electron": "^33.0.0", + "electron": "^33.4.11", "electron-builder": "^25.1.8" } }, @@ -84,7 +84,7 @@ }, "node_modules/@electron/get": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", + "resolved": "https://registry.npmmirror.com/@electron/get/-/get-2.0.3.tgz", "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", "dev": true, "license": "MIT", @@ -777,7 +777,7 @@ }, "node_modules/@types/yauzl": { "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "resolved": "https://registry.npmmirror.com/@types/yauzl/-/yauzl-2.10.3.tgz", "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "dev": true, "license": "MIT", @@ -1232,7 +1232,7 @@ }, "node_modules/boolean": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "resolved": "https://registry.npmmirror.com/boolean/-/boolean-3.2.0.tgz", "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "dev": true, @@ -1951,7 +1951,7 @@ }, "node_modules/define-data-property": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, "license": "MIT", @@ -1970,7 +1970,7 @@ }, "node_modules/define-properties": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, "license": "MIT", @@ -2016,7 +2016,7 @@ }, "node_modules/detect-node": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "resolved": "https://registry.npmmirror.com/detect-node/-/detect-node-2.1.0.tgz", "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "dev": true, "license": "MIT", @@ -2216,7 +2216,7 @@ }, "node_modules/electron": { "version": "33.4.11", - "resolved": "https://registry.npmjs.org/electron/-/electron-33.4.11.tgz", + "resolved": "https://registry.npmmirror.com/electron/-/electron-33.4.11.tgz", "integrity": "sha512-xmdAs5QWRkInC7TpXGNvzo/7exojubk+72jn1oJL7keNeIlw7xNglf8TGtJtkR4rWC5FJq0oXiIXPS9BcK2Irg==", "dev": true, "hasInstallScript": true, @@ -2502,7 +2502,7 @@ }, "node_modules/es6-error": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "resolved": "https://registry.npmmirror.com/es6-error/-/es6-error-4.1.1.tgz", "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true, "license": "MIT", @@ -2520,7 +2520,7 @@ }, "node_modules/escape-string-regexp": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", @@ -2541,7 +2541,7 @@ }, "node_modules/extract-zip": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "resolved": "https://registry.npmmirror.com/extract-zip/-/extract-zip-2.0.1.tgz", "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "dev": true, "license": "BSD-2-Clause", @@ -2587,7 +2587,7 @@ }, "node_modules/fd-slicer": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "resolved": "https://registry.npmmirror.com/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", "dev": true, "license": "MIT", @@ -2692,7 +2692,7 @@ }, "node_modules/fs-extra": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-8.1.0.tgz", "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "license": "MIT", @@ -2876,7 +2876,7 @@ }, "node_modules/global-agent": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "resolved": "https://registry.npmmirror.com/global-agent/-/global-agent-3.0.0.tgz", "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", "dev": true, "license": "BSD-3-Clause", @@ -2895,7 +2895,7 @@ }, "node_modules/global-agent/node_modules/semver": { "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", @@ -2909,7 +2909,7 @@ }, "node_modules/globalthis": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "resolved": "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.4.tgz", "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, "license": "MIT", @@ -2983,7 +2983,7 @@ }, "node_modules/has-property-descriptors": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "license": "MIT", @@ -3368,7 +3368,7 @@ }, "node_modules/json-stringify-safe": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "resolved": "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true, "license": "ISC", @@ -3389,7 +3389,7 @@ }, "node_modules/jsonfile": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, "license": "MIT", @@ -3633,7 +3633,7 @@ }, "node_modules/matcher": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "resolved": "https://registry.npmmirror.com/matcher/-/matcher-3.0.0.tgz", "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", "dev": true, "license": "MIT", @@ -4019,7 +4019,7 @@ }, "node_modules/object-keys": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true, "license": "MIT", @@ -4198,7 +4198,7 @@ }, "node_modules/pend": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "resolved": "https://registry.npmmirror.com/pend/-/pend-1.2.0.tgz", "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "dev": true, "license": "MIT" @@ -4235,7 +4235,7 @@ }, "node_modules/progress": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "resolved": "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true, "license": "MIT", @@ -4461,7 +4461,7 @@ }, "node_modules/roarr": { "version": "2.15.4", - "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "resolved": "https://registry.npmmirror.com/roarr/-/roarr-2.15.4.tgz", "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", "dev": true, "license": "BSD-3-Clause", @@ -4528,7 +4528,7 @@ }, "node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", @@ -4538,7 +4538,7 @@ }, "node_modules/semver-compare": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "resolved": "https://registry.npmmirror.com/semver-compare/-/semver-compare-1.0.0.tgz", "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", "dev": true, "license": "MIT", @@ -4546,7 +4546,7 @@ }, "node_modules/serialize-error": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "resolved": "https://registry.npmmirror.com/serialize-error/-/serialize-error-7.0.1.tgz", "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", "dev": true, "license": "MIT", @@ -4717,7 +4717,7 @@ }, "node_modules/sprintf-js": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.1.3.tgz", "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "dev": true, "license": "BSD-3-Clause", @@ -4816,7 +4816,7 @@ }, "node_modules/sumchecker": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "resolved": "https://registry.npmmirror.com/sumchecker/-/sumchecker-3.0.1.tgz", "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", "dev": true, "license": "Apache-2.0", @@ -4977,7 +4977,7 @@ }, "node_modules/type-fest": { "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.13.1.tgz", "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", "dev": true, "license": "(MIT OR CC0-1.0)", @@ -5038,7 +5038,7 @@ }, "node_modules/universalify": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, "license": "MIT", @@ -5224,7 +5224,7 @@ }, "node_modules/yauzl": { "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "resolved": "https://registry.npmmirror.com/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", "dev": true, "license": "MIT", diff --git a/libs/openagent_demo/electron/package.json b/libs/openagent_demo/electron/package.json index ce766bfe..6dbd8113 100644 --- a/libs/openagent_demo/electron/package.json +++ b/libs/openagent_demo/electron/package.json @@ -17,7 +17,7 @@ "tree-kill": "^1.2.2" }, "devDependencies": { - "electron": "^33.0.0", + "electron": "^33.4.11", "electron-builder": "^25.1.8" }, "build": { @@ -51,7 +51,10 @@ "target": [ { "target": "dmg", - "arch": ["arm64", "x64"] + "arch": [ + "arm64", + "x64" + ] } ], "icon": "resources/icon.icns", @@ -64,7 +67,9 @@ "target": [ { "target": "nsis", - "arch": ["x64"] + "arch": [ + "x64" + ] } ], "icon": "resources/icon.ico", diff --git a/libs/openagent_demo/frontend/src/components/SettingsModal.tsx b/libs/openagent_demo/frontend/src/components/SettingsModal.tsx index f9a63aeb..c815e1c2 100644 --- a/libs/openagent_demo/frontend/src/components/SettingsModal.tsx +++ b/libs/openagent_demo/frontend/src/components/SettingsModal.tsx @@ -1684,7 +1684,7 @@ function SandboxTab({ config, onConfigChange }: ConfigTabProps) { .catch(() => {}); }, [dispatch]); - // Phase 1: Lima + // Phase 1: VM backend (Lima on macOS / WSL on Windows) const [vmStatus, setVmStatus] = useState(null); const [phase1, setPhase1] = useState("checking"); const [phase1Msg, setPhase1Msg] = useState(""); @@ -1694,6 +1694,7 @@ function SandboxTab({ config, onConfigChange }: ConfigTabProps) { const [phase2, setPhase2] = useState("pending"); const [phase2Msg, setPhase2Msg] = useState(""); const [phase2Error, setPhase2Error] = useState(""); + const [vmInstanceStopped, setVmInstanceStopped] = useState(false); // Phase 3: Provision const [phase3, setPhase3] = useState("pending"); @@ -1738,7 +1739,7 @@ function SandboxTab({ config, onConfigChange }: ConfigTabProps) { } catch { /* best effort — step defs come from backend constants */ } // Phase 1 - let limaInstalled = false; + let vmBackendInstalled = false; try { const vs = await getVMStatus(); if (cancelled) return; @@ -1746,7 +1747,7 @@ function SandboxTab({ config, onConfigChange }: ConfigTabProps) { if (!vs.supported) { setPhase1("error"); setPhase1Error("Not supported on this platform"); return; } if (vs.installed) { setPhase1("done"); - limaInstalled = true; + vmBackendInstalled = true; } else { setPhase1("pending"); } @@ -1757,7 +1758,7 @@ function SandboxTab({ config, onConfigChange }: ConfigTabProps) { return; } - if (!limaInstalled) return; + if (!vmBackendInstalled) return; // Phase 2 let vmReady = false; @@ -1766,15 +1767,19 @@ function SandboxTab({ config, onConfigChange }: ConfigTabProps) { if (cancelled) return; if (bs.status === "running") { setPhase2("running"); + setVmInstanceStopped(false); attachBuild(); } else if (bs.vm_state === "Running") { setPhase2("done"); + setVmInstanceStopped(false); vmReady = true; } else if (bs.vm_state === "Stopped") { setPhase2("pending"); + setVmInstanceStopped(true); setPhase2Msg("VM exists but is stopped"); } else { setPhase2("pending"); + setVmInstanceStopped(false); } } catch { if (cancelled) return; @@ -1815,8 +1820,8 @@ function SandboxTab({ config, onConfigChange }: ConfigTabProps) { return () => { cancelled = true; }; }, []); // eslint-disable-line react-hooks/exhaustive-deps - // ── Phase 1: Install Lima ── - const handleInstallLima = async () => { + // ── Phase 1: Install VM backend ── + const handleInstallVmBackend = async () => { setPhase1("running"); setPhase1Error(""); setPhase1Msg("Starting installation..."); @@ -1846,6 +1851,7 @@ function SandboxTab({ config, onConfigChange }: ConfigTabProps) { (_step, message) => setPhase2Msg(message), () => { setPhase2("done"); + setVmInstanceStopped(false); setPhase2Msg(""); refreshAppVmStatus(); // Unblocks cowork mode in the app }, @@ -1957,6 +1963,12 @@ function SandboxTab({ config, onConfigChange }: ConfigTabProps) { } }; + const inferredBackend = navigator.platform.toUpperCase().includes("WIN") ? "wsl" : "lima"; + const vmBackend = vmStatus?.backend ?? inferredBackend; + const vmEngineLabel = vmBackend === "wsl" ? "WSL Engine" : "Lima Engine"; + const vmBackendName = vmBackend === "wsl" ? "WSL" : "Lima"; + const vmPlatformLabel = vmBackend === "wsl" ? "Windows only" : "macOS only"; + // Determine overall VM status for the card header // Cowork mode is usable once Lima is installed and VM is running (phases 1+2). // Phase 3 (dependency installation) is optional and can run in the background. @@ -2027,7 +2039,7 @@ function SandboxTab({ config, onConfigChange }: ConfigTabProps) {
Virtual Machine Cowork mode - macOS only + {vmPlatformLabel}
Local VM sandbox — runs agent code securely on your machine @@ -2056,23 +2068,26 @@ function SandboxTab({ config, onConfigChange }: ConfigTabProps) { {/* ── Setup in progress or not yet started ── */} {vmStatus && vmStatus.supported && !allDone && (
- {/* Phase 1: Lima */} + {/* Phase 1: VM backend */}
{phaseIcon(phase1)} - VM Engine + {vmEngineLabel} {phase1 === "done" && Installed} {phase1 === "running" && phase1Msg && {phase1Msg}} {phase1 === "pending" && ( - + )} {phase1 === "error" && ( - + )}
{phase1 === "error" && phase1Error && (

{phase1Error}

)} + {phase1 === "done" && ( +

{vmBackendName} is installed and available.

+ )}
{/* Phase 2: VM Build */} @@ -2083,7 +2098,9 @@ function SandboxTab({ config, onConfigChange }: ConfigTabProps) { {phase2 === "done" && Ready} {phase2 === "running" && phase2Msg && {phase2Msg}} {phase2 === "pending" && phase1 === "done" && ( - + )} {phase2 === "error" && ( From a269b8c7afcb84d3c86b22ba7d6c6d6f1c24e7ec Mon Sep 17 00:00:00 2001 From: aone Date: Tue, 24 Mar 2026 10:43:42 +0800 Subject: [PATCH 03/28] fix(demo): remove duplicate VM backend declarations in settings Resolve duplicate const declarations introduced during conflict resolution so the sandbox settings panel compiles cleanly and preserves feat/agent-loop behavior. Made-with: Cursor --- .../openagent_demo/frontend/src/components/SettingsModal.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/libs/openagent_demo/frontend/src/components/SettingsModal.tsx b/libs/openagent_demo/frontend/src/components/SettingsModal.tsx index c523997d..31e4fb22 100644 --- a/libs/openagent_demo/frontend/src/components/SettingsModal.tsx +++ b/libs/openagent_demo/frontend/src/components/SettingsModal.tsx @@ -1733,11 +1733,6 @@ function SandboxTab({ config, onConfigChange }: ConfigTabProps) { } }; - const inferredBackend = navigator.platform.toUpperCase().includes("WIN") ? "wsl" : "lima"; - const vmBackend = vmStatus?.backend ?? inferredBackend; - const vmEngineLabel = vmBackend === "wsl" ? "WSL Engine" : "Lima Engine"; - const vmBackendName = vmBackend === "wsl" ? "WSL" : "Lima"; - const vmPlatformLabel = vmBackend === "wsl" ? "Windows only" : "macOS only"; // Cowork mode is usable once Lima is installed and VM is running (phases 1+2). // Phase 3 (dependency installation) can run in the background. const vmUsable = phase1 === "done" && phase2 === "done"; From 107e4a5831f8ffe99683ec35a83145161ef58009 Mon Sep 17 00:00:00 2001 From: aone Date: Tue, 24 Mar 2026 17:16:35 +0800 Subject: [PATCH 04/28] =?UTF-8?q?mod=EF=BC=9Afix=20windows=20sandbox=20pro?= =?UTF-8?q?blem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../openagent/computer/local/_wsl.py | 55 ++++++++++-- .../openagent/langchain/middleware.py | 23 +++++ .../backend/openagent_api/agent_manager.py | 61 ++++++++++---- .../backend/openagent_api/routes/setup.py | 83 +++++++++++++++---- libs/openagent_demo/frontend/src/api.ts | 6 +- .../frontend/src/components/WelcomeScreen.tsx | 30 +++++-- libs/openagent_demo/frontend/src/vmSetup.tsx | 17 +++- 7 files changed, 223 insertions(+), 52 deletions(-) diff --git a/libs/openagent/openagent/computer/local/_wsl.py b/libs/openagent/openagent/computer/local/_wsl.py index 5724ac0d..1a57931b 100644 --- a/libs/openagent/openagent/computer/local/_wsl.py +++ b/libs/openagent/openagent/computer/local/_wsl.py @@ -45,6 +45,42 @@ _PLATFORM = sys.platform +def _resolve_wsl_exe() -> str | None: + """Return a usable ``wsl.exe`` path. + + Some hosts (notably Electron-spawned backends) omit ``System32`` from + ``PATH``, so ``shutil.which`` fails even though WSL is installed. + """ + w = shutil.which("wsl.exe") or shutil.which("wsl") + if w: + return w + system_root = os.environ.get("SystemRoot") or os.environ.get("WINDIR") + if not system_root: + system_root = r"C:\Windows" + candidate = Path(system_root) / "System32" / "wsl.exe" + if candidate.is_file(): + return str(candidate) + return None + + +def _stable_host_cwd() -> str: + """Return a safe Windows cwd for launching ``wsl.exe``. + + WSL tries to translate the parent process cwd into Linux path on every + invocation. If that cwd is a stale UNC/session path, startup prints + ``CreateProcessCommon: ... chdir(...) failed`` and commands may run in an + unexpected context. Force a stable host cwd to avoid inheriting stale + per-session paths. + """ + system_root = os.environ.get("SystemRoot") or os.environ.get("WINDIR") or r"C:\Windows" + # ``wsl.exe`` exists under System32 on supported hosts; use that directory + # as a stable cwd if available, otherwise fall back to the process cwd. + safe_dir = Path(system_root) / "System32" + if safe_dir.is_dir(): + return str(safe_dir) + return os.getcwd() + + def _ensure_proactor_event_loop() -> None: """Switch to ``ProactorEventLoop`` if not already active. @@ -81,7 +117,7 @@ def _check_wsl_prerequisites() -> None: if _PLATFORM != "win32": msg = f"WSL is a Windows subsystem — it cannot run on {_PLATFORM}" raise UnsupportedPlatformError(msg) - if not shutil.which("wsl") and not shutil.which("wsl.exe"): + if _resolve_wsl_exe() is None: msg = "wsl.exe not found. Install WSL2: https://learn.microsoft.com/windows/wsl/install" raise MissingDependencyError(msg) @@ -99,6 +135,9 @@ class WslVM: def __init__(self, instance: str) -> None: _check_wsl_prerequisites() + wsl_exe = _resolve_wsl_exe() + assert wsl_exe is not None + self._wsl_exe = wsl_exe self._instance = instance self._unc_prefix: str | None = None # cached after first successful probe @@ -133,7 +172,7 @@ async def status(self) -> str | None: WslError: If the distribution exists but is WSL version 1. """ proc = await asyncio.create_subprocess_exec( - "wsl.exe", + self._wsl_exe, "--list", "--verbose", stdout=asyncio.subprocess.PIPE, @@ -241,7 +280,7 @@ async def build(self, tarball_path: Path | str) -> None: disk_dir.mkdir(parents=True, exist_ok=True) await self._run_wsl( - "wsl.exe", + self._wsl_exe, "--import", self._instance, str(disk_dir), @@ -274,7 +313,7 @@ async def start(self) -> None: if current != "Running": # Trigger start by running a trivial command. await self._run_wsl( - "wsl.exe", + self._wsl_exe, "-d", self._instance, "--", @@ -323,7 +362,7 @@ async def stop(self) -> None: return await self._run_wsl( - "wsl.exe", + self._wsl_exe, "--terminate", self._instance, timeout=60, @@ -333,7 +372,7 @@ async def delete(self) -> None: """Unregister the WSL distribution and clean up config (best-effort).""" with contextlib.suppress(WslError): await self._run_wsl( - "wsl.exe", + self._wsl_exe, "--unregister", self._instance, ) @@ -370,7 +409,7 @@ async def shell( """ inner = f"cd {shlex.quote(cwd)} && {command}" if cwd is not None else command - exec_args: list[str] = ["wsl.exe", "-d", self._instance] + exec_args: list[str] = [self._wsl_exe, "-d", self._instance] if user is not None: exec_args += ["-u", user] exec_args += ["--", "bash"] @@ -386,6 +425,7 @@ async def shell( stdin=asyncio.subprocess.DEVNULL, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, + cwd=_stable_host_cwd(), ) try: @@ -462,6 +502,7 @@ async def _run_wsl(self, *cmd: str, timeout: float = 300) -> str: # noqa: ASYNC *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, + cwd=_stable_host_cwd(), ) try: stdout_bytes, stderr_bytes = await asyncio.wait_for( diff --git a/libs/openagent/openagent/langchain/middleware.py b/libs/openagent/openagent/langchain/middleware.py index e81d0853..9eeaada0 100644 --- a/libs/openagent/openagent/langchain/middleware.py +++ b/libs/openagent/openagent/langchain/middleware.py @@ -381,6 +381,26 @@ async def abefore_model( Group 3: Annotators (system reminders). """ messages: list[BaseMessage] = list(state["messages"]) + env_prompt_updated = False + + # Workspace path can change after agent creation (e.g. cowork warm-up + # creates the agent, then PATCH mounts the user's folder and sets + # default_cwd). Tools see the new cwd, but the system prompt still + # lists the old ``pwd`` from EnvironmentResolver — follow-up turns may + # run size/list commands against the wrong directory. Refresh when pwd + # drifts (same pattern as compaction rebuild). + if self._environment_resolver is not None and self._prompt_profile is not None: + fresh_env = await self._environment_resolver.resolve() + old_wd = self._context.environment.working_dir if self._context.environment else None + if fresh_env.working_dir != old_wd: + self._context = replace(self._context, environment=fresh_env) + new_content = compose(self._prompt_profile, self._context) + if self._custom_prompt: + new_content = f"{self._custom_prompt}\n\n{new_content}" + if messages and isinstance(messages[0], SystemMessage): + messages[0] = SystemMessage(content=new_content) + self._system_prompt = new_content + env_prompt_updated = True # --- GROUP 1: Intercepts (compaction phases) --- phase = CompactionPhase(state.get("compaction_phase", CompactionPhase.NONE)) @@ -459,6 +479,9 @@ async def abefore_model( if images_extracted: return {"messages": LangGraphOverwrite(messages)} + if env_prompt_updated: + return {"messages": LangGraphOverwrite(messages)} + return None @hook_config(can_jump_to=["model"]) diff --git a/libs/openagent_demo/backend/openagent_api/agent_manager.py b/libs/openagent_demo/backend/openagent_api/agent_manager.py index 053e3e14..23490c2d 100644 --- a/libs/openagent_demo/backend/openagent_api/agent_manager.py +++ b/libs/openagent_demo/backend/openagent_api/agent_manager.py @@ -20,6 +20,7 @@ import json import logging import os +import shlex import sys from typing import Any @@ -65,6 +66,33 @@ def _set_computer_default_cwd(computer: Any, cwd: str | None) -> None: if callable(setter): setter(cwd) + async def _verify_session_dir_writable(self, session_name: str, guest_dir: str) -> None: + """Ensure the cowork session user can write to the selected working dir.""" + if self._vm_manager is None: + return + vm = getattr(self._vm_manager, "_vm", None) + if vm is None: + return + + probe = f"{guest_dir.rstrip('/')}/.openagent_write_probe_{os.getpid()}" + cmd = ( + f"test -d {shlex.quote(guest_dir)} && " + f"test -w {shlex.quote(guest_dir)} && " + f"touch {shlex.quote(probe)} && " + f"rm -f {shlex.quote(probe)}" + ) + result = await vm.shell(cmd, user=session_name) + if result.exit_code == 0: + return + + detail = (result.stderr or result.stdout or "").strip() or "unknown error" + raise RuntimeError( + "Selected working directory is mounted but not writable for the cowork session user. " + f"guest_dir={guest_dir} session={session_name} detail={detail}. " + "Please choose a writable local folder (recommended: D:\\code\\...), " + "or adjust Windows folder ACL/WSL mount permissions." + ) + # ── Computer management ── async def _ensure_computer( @@ -123,11 +151,12 @@ async def _ensure_computer( if self._vm_manager is None: import shutil - vm_backend_ready = ( - bool(shutil.which("wsl.exe") or shutil.which("wsl")) - if sys.platform == "win32" - else bool(shutil.which("limactl")) - ) + if sys.platform == "win32": + from openagent.computer.local._wsl import _resolve_wsl_exe + + vm_backend_ready = _resolve_wsl_exe() is not None + else: + vm_backend_ready = bool(shutil.which("limactl")) if not vm_backend_ready: raise RuntimeError( "Cowork mode requires VM setup. " @@ -158,10 +187,9 @@ async def _ensure_computer( logger.info("Creating new session (mounts=%s)...", session_mounts) computer = await self._vm_manager.computer(mounts=session_mounts) if default_cwd is not None: - self._set_computer_default_cwd( - computer, - default_cwd.format(session=computer.session_name), - ) + resolved_cwd = default_cwd.format(session=computer.session_name) + self._set_computer_default_cwd(computer, resolved_cwd) + await self._verify_session_dir_writable(computer.session_name, resolved_cwd) except FileNotFoundError: raise RuntimeError( "Cowork mode requires VM setup. " @@ -433,11 +461,12 @@ def _schedule_vm_warmup(self) -> None: import shutil - vm_backend_available = ( - bool(shutil.which("wsl.exe") or shutil.which("wsl")) - if sys.platform == "win32" - else bool(shutil.which("limactl")) - ) + if sys.platform == "win32": + from openagent.computer.local._wsl import _resolve_wsl_exe + + vm_backend_available = _resolve_wsl_exe() is not None + else: + vm_backend_available = bool(shutil.which("limactl")) if not vm_backend_available: return @@ -581,7 +610,9 @@ async def mount_working_dir(self, session_name: str, working_dir: str) -> None: await self._vm_manager.mount([mount], session=session_name) self._session_working_dirs[session_name] = (working_dir, new_target) computer = self._computers.get(session_name) - self._set_computer_default_cwd(computer, f"/sessions/{session_name}/mnt/{new_target}") + resolved_cwd = f"/sessions/{session_name}/mnt/{new_target}" + self._set_computer_default_cwd(computer, resolved_cwd) + await self._verify_session_dir_writable(session_name, resolved_cwd) logger.info("Mounted working dir %s for session %s", working_dir, session_name) # Remove the stale mount-point directory left behind after unmount. diff --git a/libs/openagent_demo/backend/openagent_api/routes/setup.py b/libs/openagent_demo/backend/openagent_api/routes/setup.py index 41472442..d1b0d82a 100644 --- a/libs/openagent_demo/backend/openagent_api/routes/setup.py +++ b/libs/openagent_demo/backend/openagent_api/routes/setup.py @@ -182,7 +182,23 @@ def _lima_status() -> dict[str, object]: def _wsl_cmd() -> str | None: - return shutil.which("wsl.exe") or shutil.which("wsl") + """Resolve path to ``wsl.exe``. + + Electron / minimal service environments sometimes omit ``System32`` from + ``PATH``, which makes ``shutil.which`` fail even though WSL is installed. + Fall back to the well-known location so ``/api/setup/vm`` reports + ``installed: true`` and subprocess launches succeed. + """ + w = shutil.which("wsl.exe") or shutil.which("wsl") + if w: + return w + system_root = os.environ.get("SystemRoot") or os.environ.get("WINDIR") + if not system_root: + system_root = r"C:\Windows" + candidate = Path(system_root) / "System32" / "wsl.exe" + if candidate.is_file(): + return str(candidate) + return None def _decode_wsl_output(raw: bytes) -> str: @@ -209,10 +225,11 @@ def _parse_wsl_list(text: str) -> list[dict[str, str]]: async def _wsl_list() -> list[dict[str, str]]: - if not _wsl_cmd(): + wsl_exe = _wsl_cmd() + if not wsl_exe: return [] proc = await asyncio.create_subprocess_exec( - "wsl.exe", + wsl_exe, "--list", "--verbose", stdout=asyncio.subprocess.PIPE, @@ -232,6 +249,24 @@ async def _wsl_instance_status() -> str | None: return None +# ``wsl -l -v`` uses the Windows display language for the STATE column. +# Cowork only needs the ``openagent`` distro to exist; WSL starts it on demand. +_WSL_COWORK_READY_STATES = frozenset( + { + "Running", + "Stopped", + "正在运行", + "已停止", + } +) + + +def _wsl_distro_ready_for_cowork(state: str | None) -> bool: + if state is None: + return False + return state.strip() in _WSL_COWORK_READY_STATES + + def _wsl_status() -> dict[str, object]: wsl = _wsl_cmd() return {"installed": bool(wsl), "path": wsl, "managed": False} @@ -250,8 +285,11 @@ def _win_path_to_wsl(path: Path | str) -> str: async def _wsl_shell(cmd: str, *, timeout: float = 60) -> tuple[int, str, str]: + wsl_exe = _wsl_cmd() + if not wsl_exe: + return 1, "", "wsl.exe not found" proc = await asyncio.create_subprocess_exec( - "wsl.exe", + wsl_exe, "-d", _WSL_INSTANCE, "--", @@ -348,8 +386,10 @@ def sse(event: str, data: dict[str, object]) -> str: return yield sse("progress", {"step": "installing", "message": "Installing WSL components..."}) + # ``wsl.exe`` may exist in System32 even when not on PATH + wsl_for_install = _wsl_cmd() or str(Path(os.environ.get("SystemRoot", r"C:\Windows")) / "System32" / "wsl.exe") proc = await asyncio.create_subprocess_exec( - "wsl.exe", + wsl_for_install, "--install", "--no-distribution", stdout=asyncio.subprocess.PIPE, @@ -407,22 +447,23 @@ def _vm_status() -> dict[str, object]: async def get_vm_status() -> dict[str, object]: """Check whether the VM backend is available. - Returns ``vm_ready: true`` only when the backend is installed AND - the VM instance is running — i.e. cowork mode can start sessions - immediately. + Returns ``vm_ready: true`` when cowork can start: Lima needs the instance + **Running**; WSL accepts **Running** or **Stopped** (distro exists — WSL + starts it on first ``wsl -d``). Localized ``wsl -l -v`` state strings are + recognized where known. """ result = _vm_status() - # Quick readiness check: installed + instance running? vm_ready = False instance_status: str | None = None if result.get("installed"): try: if result.get("backend") == "lima": instance_status = await _lima_instance_status() + vm_ready = instance_status == "Running" elif result.get("backend") == "wsl": instance_status = await _wsl_instance_status() - vm_ready = instance_status == "Running" + vm_ready = _wsl_distro_ready_for_cowork(instance_status) except Exception: pass @@ -660,7 +701,8 @@ async def _run_lima(self) -> None: self._error = f"exit {proc.returncode}" async def _run_wsl(self) -> None: - if not _wsl_cmd(): + wsl_exe = _wsl_cmd() + if not wsl_exe: self._emit("error", {"message": "WSL is not installed. Install it first in Phase 1."}) self._status = "error" self._error = "WSL missing" @@ -675,7 +717,7 @@ async def _run_wsl(self) -> None: if status == "Stopped": self._emit("progress", {"step": "starting", "message": "Starting existing WSL distro..."}) proc = await asyncio.create_subprocess_exec( - "wsl.exe", "-d", _WSL_INSTANCE, "--", "echo", "ok", + wsl_exe, "-d", _WSL_INSTANCE, "--", "echo", "ok", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) @@ -697,7 +739,7 @@ async def _run_wsl(self) -> None: has_source = any(e["name"].lower() == _WSL_EXPORT_SOURCE.lower() for e in entries) if not has_source: proc = await asyncio.create_subprocess_exec( - "wsl.exe", "--install", "-d", _WSL_EXPORT_SOURCE, + wsl_exe, "--install", "-d", _WSL_EXPORT_SOURCE, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) @@ -722,7 +764,7 @@ async def _run_wsl(self) -> None: self._emit("progress", {"step": "creating", "message": "Exporting Ubuntu rootfs..."}) proc_export = await asyncio.create_subprocess_exec( - "wsl.exe", "--export", _WSL_EXPORT_SOURCE, str(export_tar), + wsl_exe, "--export", _WSL_EXPORT_SOURCE, str(export_tar), stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) @@ -737,7 +779,7 @@ async def _run_wsl(self) -> None: self._emit("progress", {"step": "creating", "message": "Importing OpenAgent WSL distro..."}) proc_import = await asyncio.create_subprocess_exec( - "wsl.exe", "--import", _WSL_INSTANCE, str(import_dir), str(export_tar), "--version", "2", + wsl_exe, "--import", _WSL_INSTANCE, str(import_dir), str(export_tar), "--version", "2", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) @@ -752,7 +794,7 @@ async def _run_wsl(self) -> None: self._emit("progress", {"step": "starting", "message": "Starting OpenAgent WSL distro..."}) proc_start = await asyncio.create_subprocess_exec( - "wsl.exe", "-d", _WSL_INSTANCE, "--", "echo", "ok", + wsl_exe, "-d", _WSL_INSTANCE, "--", "echo", "ok", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) @@ -945,8 +987,15 @@ async def _run_wsl(self, **kwargs: object) -> None: if force: cmd += " --force" + wsl_exe = _wsl_cmd() + if not wsl_exe: + self._emit("error", {"message": "wsl.exe not found — cannot provision"}) + self._status = "error" + self._error = "WSL missing" + return + proc = await asyncio.create_subprocess_exec( - "wsl.exe", "-d", _WSL_INSTANCE, "--", "bash", "-lc", cmd, + wsl_exe, "-d", _WSL_INSTANCE, "--", "bash", "-lc", cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) diff --git a/libs/openagent_demo/frontend/src/api.ts b/libs/openagent_demo/frontend/src/api.ts index c9a6abc1..204ad387 100644 --- a/libs/openagent_demo/frontend/src/api.ts +++ b/libs/openagent_demo/frontend/src/api.ts @@ -63,7 +63,11 @@ export async function updateWarmSession(sessionId: string, updates: { working_di headers: { "Content-Type": "application/json" }, body: JSON.stringify(updates), }); - if (!res.ok) throw new Error(`Failed to update session: ${res.statusText}`); + if (!res.ok) { + const detail = await res.json().catch(() => null); + const msg = detail?.detail || res.statusText || `HTTP ${res.status}`; + throw new Error(`Failed to update session (${res.status}): ${msg}`); + } return res.json(); } diff --git a/libs/openagent_demo/frontend/src/components/WelcomeScreen.tsx b/libs/openagent_demo/frontend/src/components/WelcomeScreen.tsx index d26757a0..f7c60c68 100644 --- a/libs/openagent_demo/frontend/src/components/WelcomeScreen.tsx +++ b/libs/openagent_demo/frontend/src/components/WelcomeScreen.tsx @@ -50,6 +50,7 @@ export default function WelcomeScreen({ onSubmit, mode, onOpenSettings }: Welcom const [current, setCurrent] = useState(0); const [prev, setPrev] = useState(null); const [selectedFolder, setSelectedFolder] = useState(""); + const [mountingFolder, setMountingFolder] = useState(false); const [pendingFiles, setPendingFiles] = useState([]); const textareaRef = useRef(null); const fileRef = useRef(null); @@ -116,7 +117,7 @@ export default function WelcomeScreen({ onSubmit, mode, onOpenSettings }: Welcom if (sandboxBlocked) { flashE2bHint(); return; } const trimmed = value.trim(); const hasContent = trimmed || doneFiles.length > 0; - if (!hasContent || anyUploading) return; + if (!hasContent || anyUploading || mountingFolder) return; const opts: { workingDir?: string; attachments?: Attachment[] } = {}; if (selectedFolder) opts.workingDir = selectedFolder; if (doneFiles.length > 0) { @@ -125,7 +126,7 @@ export default function WelcomeScreen({ onSubmit, mode, onOpenSettings }: Welcom onSubmit(trimmed, Object.keys(opts).length > 0 ? opts : undefined); setValue(""); setPendingFiles([]); - }, [value, onSubmit, selectedFolder, doneFiles, anyUploading, sandboxBlocked, flashE2bHint]); + }, [value, onSubmit, selectedFolder, doneFiles, anyUploading, mountingFolder, sandboxBlocked, flashE2bHint]); const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { @@ -196,9 +197,15 @@ export default function WelcomeScreen({ onSubmit, mode, onOpenSettings }: Welcom setSelectedFolder(folder); if (warmSessionId && folder) { // Mount the folder in the warm session - updateWarmSession(warmSessionId, { working_dir: folder }).catch(() => { - dispatch({ type: "SHOW_NOTIFICATION", payload: { message: "Failed to mount folder", type: "error" } }); - }); + setMountingFolder(true); + updateWarmSession(warmSessionId, { working_dir: folder }) + .catch((err: unknown) => { + const msg = err instanceof Error ? err.message : String(err); + // Benign race: warm session may be claimed by conversation creation. + if (msg.includes("(404)") && msg.includes("Session was claimed")) return; + dispatch({ type: "SHOW_NOTIFICATION", payload: { message: "Failed to mount folder", type: "error" } }); + }) + .finally(() => setMountingFolder(false)); } // If warmSessionId isn't ready yet, the effect below will flush when it arrives }, [warmSessionId, dispatch, vmNotReady, flashE2bHint]); @@ -206,9 +213,14 @@ export default function WelcomeScreen({ onSubmit, mode, onOpenSettings }: Welcom // Flush pending folder mount when warm session becomes available useEffect(() => { if (warmSessionId && selectedFolder) { - updateWarmSession(warmSessionId, { working_dir: selectedFolder }).catch(() => { - dispatch({ type: "SHOW_NOTIFICATION", payload: { message: "Failed to mount folder", type: "error" } }); - }); + setMountingFolder(true); + updateWarmSession(warmSessionId, { working_dir: selectedFolder }) + .catch((err: unknown) => { + const msg = err instanceof Error ? err.message : String(err); + if (msg.includes("(404)") && msg.includes("Session was claimed")) return; + dispatch({ type: "SHOW_NOTIFICATION", payload: { message: "Failed to mount folder", type: "error" } }); + }) + .finally(() => setMountingFolder(false)); } // Only trigger when warmSessionId changes (not on every folder change — // handleFolderChange already handles that when the session is ready) @@ -354,7 +366,7 @@ export default function WelcomeScreen({ onSubmit, mode, onOpenSettings }: Welcom + {isPreparingRequest && ( +
+ + Preparing request... +
+ )} {missingE2bKey && (
E2B API key required —{" "} diff --git a/libs/openagent_demo/frontend/src/components/WelcomeScreen.tsx b/libs/openagent_demo/frontend/src/components/WelcomeScreen.tsx index f7c60c68..5785f74a 100644 --- a/libs/openagent_demo/frontend/src/components/WelcomeScreen.tsx +++ b/libs/openagent_demo/frontend/src/components/WelcomeScreen.tsx @@ -25,6 +25,7 @@ interface WelcomeScreenProps { export default function WelcomeScreen({ onSubmit, mode, onOpenSettings }: WelcomeScreenProps) { const { state, dispatch } = useAppContext(); const warmSessionId = state.warmSessionId; + const isPreparingRequest = state.isRequestPending; const isCowork = mode === "cowork"; const noModels = !state.serverConfig?.models?.length; const missingE2bKey = !isCowork && !state.serverConfig?.sandbox?.e2b_api_key; @@ -112,12 +113,22 @@ export default function WelcomeScreen({ onSubmit, mode, onOpenSettings }: Welcom const doneFiles = pendingFiles.filter((f) => f.status === "done"); const anyUploading = pendingFiles.some((f) => f.status === "uploading"); + const sendDisabled = (!value.trim() && doneFiles.length === 0) || anyUploading || mountingFolder || isPreparingRequest || noModels || sandboxBlocked; + const sendTitle = noModels + ? "Configure a model in Settings first" + : mountingFolder + ? "Mounting folder..." + : isPreparingRequest + ? "Preparing request..." + : sandboxBlocked + ? "Sandbox setup required" + : "Send message"; const handleSubmit = useCallback(() => { if (sandboxBlocked) { flashE2bHint(); return; } const trimmed = value.trim(); const hasContent = trimmed || doneFiles.length > 0; - if (!hasContent || anyUploading || mountingFolder) return; + if (!hasContent || anyUploading || mountingFolder || isPreparingRequest) return; const opts: { workingDir?: string; attachments?: Attachment[] } = {}; if (selectedFolder) opts.workingDir = selectedFolder; if (doneFiles.length > 0) { @@ -126,7 +137,7 @@ export default function WelcomeScreen({ onSubmit, mode, onOpenSettings }: Welcom onSubmit(trimmed, Object.keys(opts).length > 0 ? opts : undefined); setValue(""); setPendingFiles([]); - }, [value, onSubmit, selectedFolder, doneFiles, anyUploading, mountingFolder, sandboxBlocked, flashE2bHint]); + }, [value, onSubmit, selectedFolder, doneFiles, anyUploading, mountingFolder, isPreparingRequest, sandboxBlocked, flashE2bHint]); const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { @@ -366,11 +377,23 @@ export default function WelcomeScreen({ onSubmit, mode, onOpenSettings }: Welcom + {mountingFolder && ( +
+ + 正在挂载目录... +
+ )} + {!mountingFolder && isPreparingRequest && ( +
+ + 正在准备请求... +
+ )} {sandboxBlocked && (
{missingE2bKey ? "E2B API key required" : "VM setup required"} —{" "} diff --git a/libs/openagent_demo/frontend/src/store.ts b/libs/openagent_demo/frontend/src/store.ts index 27f6b414..80bbe71b 100644 --- a/libs/openagent_demo/frontend/src/store.ts +++ b/libs/openagent_demo/frontend/src/store.ts @@ -20,6 +20,8 @@ export interface AppState { activeConversationId: string | null; /** Pre-conversation warm session ID (exists before first message). */ warmSessionId: string | null; + /** True after user submits until SSE stream starts (or request fails). */ + isRequestPending: boolean; isStreaming: boolean; streamingBlocks: ContentBlock[]; streamingMessageId: string | null; @@ -47,6 +49,7 @@ export const initialState: AppState = { conversations: [], activeConversationId: null, warmSessionId: null, + isRequestPending: false, isStreaming: false, streamingBlocks: [], streamingMessageId: null, @@ -74,6 +77,8 @@ export type Action = | { type: "DELETE_CONVERSATION"; payload: string } | { type: "SET_ACTIVE_CONVERSATION"; payload: string | null } | { type: "SET_WARM_SESSION"; payload: string | null } + | { type: "REQUEST_START" } + | { type: "REQUEST_END" } | { type: "ADD_USER_MESSAGE"; payload: { conversationId: string; message: Message } } | { type: "STREAM_START"; payload: { messageId: string } } | { type: "STREAM_TEXT_DELTA"; payload: string } @@ -506,6 +511,12 @@ export function reducer(state: AppState, action: Action): AppState { case "SET_WARM_SESSION": return { ...state, warmSessionId: action.payload }; + case "REQUEST_START": + return { ...state, isRequestPending: true }; + + case "REQUEST_END": + return { ...state, isRequestPending: false }; + case "ADD_USER_MESSAGE": return { ...state, @@ -525,6 +536,7 @@ export function reducer(state: AppState, action: Action): AppState { }; return { ...state, + isRequestPending: false, isStreaming: true, streamingBlocks: [], streamingMessageId: action.payload.messageId, @@ -632,6 +644,7 @@ export function reducer(state: AppState, action: Action): AppState { const streamConvId = state.streamingConversationId; return { ...state, + isRequestPending: false, isStreaming: false, streamingBlocks: [], streamingMessageId: null, @@ -655,6 +668,7 @@ export function reducer(state: AppState, action: Action): AppState { case "STREAM_ERROR": return { ...state, + isRequestPending: false, isStreaming: false, streamingConversationId: null, streamingBlocks: appendTextDelta( diff --git a/libs/openagent_demo/start-dev.ps1 b/libs/openagent_demo/start-dev.ps1 new file mode 100644 index 00000000..350a7964 --- /dev/null +++ b/libs/openagent_demo/start-dev.ps1 @@ -0,0 +1,71 @@ +param( + [switch]$CurrentWindow +) + +$ErrorActionPreference = "Stop" + +function Test-CommandExists { + param([Parameter(Mandatory = $true)][string]$Name) + return [bool](Get-Command $Name -ErrorAction SilentlyContinue) +} + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$BackendDir = Join-Path $ScriptDir "backend" +$FrontendDir = Join-Path $ScriptDir "frontend" + +if (-not (Test-Path $BackendDir)) { + throw "Backend directory not found: $BackendDir" +} +if (-not (Test-Path $FrontendDir)) { + throw "Frontend directory not found: $FrontendDir" +} + +if (-not (Test-CommandExists -Name "uv")) { + throw "Command 'uv' not found. Please install uv first." +} +if (-not (Test-CommandExists -Name "npm")) { + throw "Command 'npm' not found. Please install Node.js/npm first." +} + +$backendCmd = @" +Set-Location '$BackendDir' +if (-not (Test-Path '.venv')) { + Write-Host '[backend] .venv not found, running uv sync...' -ForegroundColor Yellow + uv sync +} +Write-Host '[backend] starting on http://127.0.0.1:8000' -ForegroundColor Cyan +uv run uvicorn openagent_api.main:app --host 127.0.0.1 --port 8000 +"@ + +$frontendCmd = @" +Set-Location '$FrontendDir' +if (-not (Test-Path 'node_modules')) { + Write-Host '[frontend] node_modules not found, running npm install...' -ForegroundColor Yellow + npm install +} +Write-Host '[frontend] starting on http://localhost:3000' -ForegroundColor Cyan +npm run dev +"@ + +if ($CurrentWindow) { + Write-Host "Starting backend in a background job (CurrentWindow mode)..." -ForegroundColor Green + Start-Job -Name "openagent-backend" -ScriptBlock { + param($cmd) + powershell -NoProfile -NoExit -Command $cmd + } -ArgumentList $backendCmd | Out-Null + + Write-Host "Starting frontend in current window..." -ForegroundColor Green + Invoke-Expression $frontendCmd + exit 0 +} + +Write-Host "Launching backend and frontend in new PowerShell windows..." -ForegroundColor Green +Start-Process powershell -ArgumentList "-NoProfile", "-NoExit", "-ExecutionPolicy", "Bypass", "-Command", $backendCmd | Out-Null +Start-Process powershell -ArgumentList "-NoProfile", "-NoExit", "-ExecutionPolicy", "Bypass", "-Command", $frontendCmd | Out-Null + +Write-Host "" +Write-Host "OpenAgent dev services launched." -ForegroundColor Green +Write-Host "Frontend: http://localhost:3000" +Write-Host "Backend : http://127.0.0.1:8000/health" +Write-Host "" +Write-Host "Tip: run with -CurrentWindow if you want frontend in the current shell." From 44684e4a5a358d976d8bd0713631cb9d6333988c Mon Sep 17 00:00:00 2001 From: xuelin-cell Date: Wed, 25 Mar 2026 21:25:39 +0800 Subject: [PATCH 07/28] fix(vm): harden WSL setup flow and rebuild prebuilt pipeline --- .gitattributes | 2 + libs/openagent/sandbox/vm/setup/setup.sh | 143 ++++++-- .../sandbox/vm/setup/steps/03_apt.sh | 68 +--- .../sandbox/vm/setup/steps/04_npm.sh | 34 +- .../sandbox/vm/setup/steps/05_pip.sh | 64 +--- .../sandbox/vm/setup/steps/06_playwright.sh | 38 +- .../backend/openagent_api/routes/setup.py | 327 ++++++++++++++++-- libs/openagent_demo/electron/main.js | 66 ++++ libs/openagent_demo/electron/preload.js | 1 + .../electron/scripts/build-all.ps1 | 9 +- .../electron/scripts/build-backend.ps1 | 66 ++-- .../electron/scripts/prepare-wsl-prebuilt.ps1 | 37 ++ .../frontend/src/components/SettingsModal.tsx | 15 +- .../openagent_demo/frontend/src/electron.d.ts | 10 + libs/openagent_demo/frontend/src/vmSetup.tsx | 98 ++++++ 15 files changed, 754 insertions(+), 224 deletions(-) create mode 100644 .gitattributes create mode 100644 libs/openagent_demo/electron/scripts/prepare-wsl-prebuilt.ps1 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..0793bb27 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +libs/openagent/sandbox/vm/setup/*.sh text eol=lf +libs/openagent/sandbox/vm/setup/steps/*.sh text eol=lf diff --git a/libs/openagent/sandbox/vm/setup/setup.sh b/libs/openagent/sandbox/vm/setup/setup.sh index 7d62c09f..18454caa 100755 --- a/libs/openagent/sandbox/vm/setup/setup.sh +++ b/libs/openagent/sandbox/vm/setup/setup.sh @@ -1,6 +1,6 @@ #!/bin/bash # ============================================================================= -# OpenAgent VM Setup — Orchestrator +# OpenAgent VM Setup -Orchestrator # ============================================================================= # Discovers and runs step scripts in order with progress reporting, # resumability (marker files), concurrency protection (flock), and @@ -21,7 +21,7 @@ set -uo pipefail # No -e: we handle errors per-step. -# ── Constants ──────────────────────────────────────────────────────────────── +# ------ Constants ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" STEPS_DIR="${SCRIPT_DIR}/steps" MARKER_DIR="/var/lib/openagent/setup" @@ -29,18 +29,31 @@ LOG_DIR="/var/log/openagent/setup" LOCK_FILE="/var/run/openagent-setup.lock" LOCK_FD=9 -# ── Environment (inherited by steps) ──────────────────────────────────────── +# ------ Environment (inherited by steps) ------------------------------------------------------------------------------------------------------------------------ export DEBIAN_FRONTEND=noninteractive export PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsers export MARKER_DIR LOG_DIR - -# ── CLI defaults ───────────────────────────────────────────────────────────── +if grep -qi microsoft /proc/version 2>/dev/null; then + _OPENAGENT_DEFAULT_CN_MIRRORS=1 +else + _OPENAGENT_DEFAULT_CN_MIRRORS=0 +fi +OPENAGENT_USE_CN_MIRRORS="${OPENAGENT_USE_CN_MIRRORS:-${_OPENAGENT_DEFAULT_CN_MIRRORS}}" +OPENAGENT_APT_MIRROR="${OPENAGENT_APT_MIRROR:-https://mirrors.ustc.edu.cn/ubuntu}" +OPENAGENT_APT_PORTS_MIRROR="${OPENAGENT_APT_PORTS_MIRROR:-https://mirrors.ustc.edu.cn/ubuntu-ports}" +OPENAGENT_PIP_INDEX_URL="${OPENAGENT_PIP_INDEX_URL:-https://pypi.tuna.tsinghua.edu.cn/simple}" +OPENAGENT_NPM_REGISTRY="${OPENAGENT_NPM_REGISTRY:-https://registry.npmmirror.com}" +OPENAGENT_PLAYWRIGHT_DOWNLOAD_HOST="${OPENAGENT_PLAYWRIGHT_DOWNLOAD_HOST:-https://npmmirror.com/mirrors/playwright}" +export OPENAGENT_USE_CN_MIRRORS OPENAGENT_APT_MIRROR OPENAGENT_APT_PORTS_MIRROR +export OPENAGENT_PIP_INDEX_URL OPENAGENT_NPM_REGISTRY OPENAGENT_PLAYWRIGHT_DOWNLOAD_HOST + +# ------ CLI defaults --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- FORCE=false SINGLE_STEP="" LIST_ONLY=false RESET=false -# ── CLI parsing ────────────────────────────────────────────────────────────── +# ------ CLI parsing ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ usage() { echo "Usage: sudo bash setup.sh [OPTIONS]" echo "" @@ -63,16 +76,16 @@ while [[ $# -gt 0 ]]; do esac done -# ── Root check ─────────────────────────────────────────────────────────────── +# ------ Root check --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- if [[ "$(id -u)" -ne 0 ]]; then echo "ERROR: Must run as root (sudo)." >&2 exit 1 fi -# ── Directory setup ────────────────────────────────────────────────────────── +# ------ Directory setup ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ mkdir -p "$MARKER_DIR" "$LOG_DIR" -# ── emit() — progress protocol ────────────────────────────────────────────── +# ------ emit() -progress protocol ------------------------------------------------------------------------------------------------------------------------------------------ # Writes to fd 3 which points to the original stdout (what the backend reads). # All other output (package managers) goes to the log file. emit() { @@ -82,7 +95,7 @@ emit() { } export -f emit -# ── apt_install — retry wrapper ────────────────────────────────────────────── +# ------ apt_install -retry wrapper ------------------------------------------------------------------------------------------------------------------------------------------ apt_install() { local max_attempts=5 local delay=3 @@ -131,22 +144,34 @@ apt_install() { } export -f apt_install -# ── pip_install — retry wrapper ────────────────────────────────────────────── +# ------ pip_install -retry wrapper ------------------------------------------------------------------------------------------------------------------------------------------ pip_install() { local max_attempts=5 local delay=5 local attempt=1 + local use_cn_mirrors="${OPENAGENT_USE_CN_MIRRORS:-0}" local pip_opts=( - --break-system-packages --timeout 120 --retries 3 + --no-cache-dir ) + if pip3 help install 2>/dev/null | grep -q -- "--break-system-packages"; then + pip_opts+=(--break-system-packages) + fi while [[ $attempt -le $max_attempts ]]; do echo ">>> pip install attempt $attempt/$max_attempts (${#} packages)" if pip3 install "${pip_opts[@]}" "$@"; then return 0 fi + if [[ "$use_cn_mirrors" == "1" ]]; then + echo ">>> Mirror install failed, retrying with official PyPI..." + if PIP_INDEX_URL="https://pypi.org/simple" \ + PIP_EXTRA_INDEX_URL="" \ + pip3 install "${pip_opts[@]}" "$@"; then + return 0 + fi + fi echo ">>> Attempt $attempt failed. Retrying in ${delay}s..." sleep $delay delay=$((delay * 2)) @@ -164,6 +189,14 @@ pip_install() { pkg_ok=true break fi + if [[ "$use_cn_mirrors" == "1" ]]; then + if PIP_INDEX_URL="https://pypi.org/simple" \ + PIP_EXTRA_INDEX_URL="" \ + pip3 install "${pip_opts[@]}" "$pkg" 2>&1; then + pkg_ok=true + break + fi + fi echo ">>> Failed: $pkg (attempt $pkg_attempt/3)" sleep $((pkg_attempt * 3)) pkg_attempt=$((pkg_attempt + 1)) @@ -182,11 +215,61 @@ pip_install() { } export -f pip_install -# ── Marker helpers ─────────────────────────────────────────────────────────── +configure_cn_mirrors() { + if [[ "${OPENAGENT_USE_CN_MIRRORS}" != "1" ]]; then + return 0 + fi + + emit _meta progress "Applying China mirrors (APT/PIP/NPM/Playwright)" + + # sed replacement escapes for arbitrary mirror strings (e.g. containing '&' or '|') + local apt_mirror_esc apt_ports_mirror_esc + apt_mirror_esc="${OPENAGENT_APT_MIRROR//\\/\\\\}" + apt_mirror_esc="${apt_mirror_esc//&/\\&}" + apt_mirror_esc="${apt_mirror_esc//|/\\|}" + apt_ports_mirror_esc="${OPENAGENT_APT_PORTS_MIRROR//\\/\\\\}" + apt_ports_mirror_esc="${apt_ports_mirror_esc//&/\\&}" + apt_ports_mirror_esc="${apt_ports_mirror_esc//|/\\|}" + + if [[ -f /etc/apt/sources.list ]]; then + cp -n /etc/apt/sources.list /etc/apt/sources.list.openagent.bak 2>/dev/null || true + sed -Ei \ + -e "s|https?://(archive|security)\.ubuntu\.com/ubuntu|${apt_mirror_esc}|g" \ + -e "s|https?://ports\.ubuntu\.com/ubuntu-ports|${apt_ports_mirror_esc}|g" \ + /etc/apt/sources.list 2>/dev/null || true + fi + + if [[ -f /etc/apt/sources.list.d/ubuntu.sources ]]; then + cp -n /etc/apt/sources.list.d/ubuntu.sources /etc/apt/sources.list.d/ubuntu.sources.openagent.bak 2>/dev/null || true + sed -Ei \ + -e "s|^URIs:[[:space:]]*https?://(archive|security)\.ubuntu\.com/ubuntu/?$|URIs: ${apt_mirror_esc}|g" \ + -e "s|^URIs:[[:space:]]*https?://ports\.ubuntu\.com/ubuntu-ports/?$|URIs: ${apt_ports_mirror_esc}|g" \ + /etc/apt/sources.list.d/ubuntu.sources 2>/dev/null || true + fi + + cat >/etc/pip.conf </etc/profile.d/openagent-mirrors.sh < "${MARKER_DIR}/$1.done"; } -# ── Step discovery ─────────────────────────────────────────────────────────── +# ------ Step discovery --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- discover_steps() { for f in "${STEPS_DIR}"/*.sh; do [[ -f "$f" ]] || continue @@ -199,41 +282,41 @@ step_desc() { sed -n '2s/^# *//p' "$1" } -# ── --reset ────────────────────────────────────────────────────────────────── +# ------ --reset ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ if [[ "$RESET" == true ]]; then rm -f "${MARKER_DIR}"/*.done echo "All markers cleared." exit 0 fi -# ── --list ─────────────────────────────────────────────────────────────────── +# ------ --list --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- if [[ "$LIST_ONLY" == true ]]; then while IFS= read -r step_file; do step_id="$(basename "$step_file" .sh)" if step_done "$step_id"; then echo "[done] $step_id ($(cat "${MARKER_DIR}/${step_id}.done"))" else - echo "[pending] $step_id — $(step_desc "$step_file")" + echo "[pending] $step_id -$(step_desc "$step_file")" fi done < <(discover_steps) exit 0 fi -# ── Concurrency lock ───────────────────────────────────────────────────────── +# ------ Concurrency lock --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- exec 9>"$LOCK_FILE" if ! flock -n $LOCK_FD; then echo "ERROR: Another setup instance is running (lockfile: $LOCK_FILE)" >&2 exit 1 fi -# ── fd redirection ─────────────────────────────────────────────────────────── -# fd 3 = original stdout → backend reads @@SETUP: lines from here -# stdout + stderr → log file (all package manager noise) +# ------ fd redirection --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +# fd 3 = original stdout -backend reads @@SETUP: lines from here +# stdout + stderr -log file (all package manager noise) LOGFILE="${LOG_DIR}/setup-$(date +%Y%m%d-%H%M%S).log" exec 3>&1 exec 1>>"$LOGFILE" 2>&1 -# ── Signal handling ────────────────────────────────────────────────────────── +# ------ Signal handling ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ HEARTBEAT_PID="" cleanup() { @@ -249,7 +332,7 @@ cancelled() { } trap cancelled SIGTERM SIGINT -# ── Heartbeat ──────────────────────────────────────────────────────────────── +# ------ Heartbeat ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ start_heartbeat() { local step_id="$1" ( @@ -269,7 +352,7 @@ stop_heartbeat() { fi } -# ── Preflight ──────────────────────────────────────────────────────────────── +# ------ Preflight ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ preflight() { emit _meta start "Preflight checks" @@ -281,7 +364,9 @@ preflight() { esac export ARCH - # Disk space (require ≥10 GB free on /) + configure_cn_mirrors + + # Disk space (require >= 10 GB free on /) local free_kb free_kb=$(df / --output=avail | tail -1 | tr -d ' ') if (( free_kb < 10485760 )); then @@ -292,7 +377,7 @@ preflight() { emit _meta done "Preflight OK (arch=$ARCH, free=$((free_kb / 1024))MB)" } -# ── run_step ───────────────────────────────────────────────────────────────── +# ------ run_step --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- run_step() { local step_file="$1" local step_id @@ -325,12 +410,12 @@ run_step() { mark_done "$step_id" emit "$step_id" done "Completed in ${elapsed}s" else - emit "$step_id" error "Failed (exit $rc) after ${elapsed}s — see $LOGFILE" + emit "$step_id" error "Failed (exit $rc) after ${elapsed}s - see $LOGFILE" return $rc fi } -# ── Main ───────────────────────────────────────────────────────────────────── +# ------ Main --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- main() { preflight @@ -362,7 +447,7 @@ main() { done < <(discover_steps) if [[ $failed -gt 0 ]]; then - emit _meta error "Setup failed — re-run to resume from failed step" + emit _meta error "Setup failed - re-run to resume from failed step" exit 1 fi diff --git a/libs/openagent/sandbox/vm/setup/steps/03_apt.sh b/libs/openagent/sandbox/vm/setup/steps/03_apt.sh index 8e1929a0..82bec2e1 100755 --- a/libs/openagent/sandbox/vm/setup/steps/03_apt.sh +++ b/libs/openagent/sandbox/vm/setup/steps/03_apt.sh @@ -1,71 +1,31 @@ #!/bin/bash -# System packages (core utils, Python, Java, PDF, LaTeX, fonts, etc.) +# System packages (minimal baseline; install extras on demand) set -uo pipefail # No -e: apt_install handles its own errors with retries. apt-get update -emit 03_apt progress "group=core_utils (17 packages)" +emit 03_apt progress "group=core_utils" apt_install \ - bash coreutils wget curl git zip unzip bzip2 xz-utils \ - file findutils patch perl jq tree sqlite3 ripgrep \ - netcat-openbsd apt-transport-https software-properties-common + bash coreutils wget curl git zip unzip jq tree ripgrep \ + file findutils patch sqlite3 || exit 1 -emit 03_apt progress "group=build_tools (2 packages)" -apt_install build-essential pkg-config +emit 03_apt progress "group=build_tools" +apt_install build-essential pkg-config || exit 1 -emit 03_apt progress "group=python (5 packages)" -apt_install python3 python3-dev python3-pip python3-venv pipx +emit 03_apt progress "group=python" +apt_install python3 python3-dev python3-pip python3-venv pipx || exit 1 -emit 03_apt progress "group=java (1 package)" -apt_install default-jre-headless +emit 03_apt progress "group=media" +apt_install imagemagick graphviz || exit 1 -emit 03_apt progress "group=pdf_tools (5 packages)" -apt_install poppler-utils qpdf pdftk-java wkhtmltopdf ghostscript - -emit 03_apt progress "group=pandoc (1 package)" -apt_install pandoc - -emit 03_apt progress "group=libreoffice (5 packages)" -apt_install \ - libreoffice-writer libreoffice-calc libreoffice-impress \ - libreoffice-common libreoffice-java-common - -emit 03_apt progress "group=media (3 packages)" -apt_install imagemagick graphviz ffmpeg - -emit 03_apt progress "group=ocr (2 packages)" -apt_install tesseract-ocr tesseract-ocr-eng - -emit 03_apt progress "group=latex (9 packages)" -apt_install \ - texlive-base texlive-latex-base texlive-latex-recommended \ - texlive-latex-extra texlive-fonts-recommended texlive-xetex \ - texlive-science texlive-pictures latexmk - -emit 03_apt progress "group=fonts (12 packages)" -apt_install \ - fonts-liberation2 fonts-dejavu fonts-freefont-ttf \ - fonts-noto-cjk fonts-noto-color-emoji \ - fonts-crosextra-caladea fonts-crosextra-carlito \ - fonts-lmodern fonts-texgyre fonts-opensymbol \ - fonts-wqy-zenhei fonts-ipafont-gothic - -emit 03_apt progress "group=x11_display (5 packages)" -apt_install \ - xvfb x11-xkb-utils xfonts-scalable xfonts-cyrillic xfonts-utils - -emit 03_apt progress "group=browser_libs (17 packages)" +emit 03_apt progress "group=fonts" apt_install \ - libnss3 libnss3-tools libatk1.0-0t64 libatk-bridge2.0-0t64 \ - libcups2t64 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \ - libxfixes3 libxrandr2 libgbm1 libasound2t64 libpango-1.0-0 \ - libcairo2 libatspi2.0-0t64 libgtk-3-0t64 libgtk-4-1 + fonts-liberation2 fonts-dejavu || exit 1 -emit 03_apt progress "group=dev_libs (7 packages)" +emit 03_apt progress "group=dev_libs" apt_install \ - libffi-dev zlib1g-dev libpng-dev libfreetype-dev libcairo2-dev \ - libglib2.0-dev libbz2-dev + libffi-dev zlib1g-dev libpng-dev libfreetype-dev libbz2-dev || exit 1 # Cleanup emit 03_apt progress "Cleaning apt cache" diff --git a/libs/openagent/sandbox/vm/setup/steps/04_npm.sh b/libs/openagent/sandbox/vm/setup/steps/04_npm.sh index 2d6fd675..014e24f0 100755 --- a/libs/openagent/sandbox/vm/setup/steps/04_npm.sh +++ b/libs/openagent/sandbox/vm/setup/steps/04_npm.sh @@ -1,31 +1,11 @@ #!/bin/bash -# NPM global packages +# NPM global packages (minimal baseline; install extras on demand) set -euo pipefail -emit 04_npm progress "Installing npm global packages" -npm install -g \ - docx@9 \ - pptxgenjs@4.0.1 \ - pdf-lib@1.17.1 \ - pdfjs-dist \ - marked \ - markdown-toc \ - markdownlint-cli \ - markdownlint-cli2 \ - remark-cli \ - remark-preset-lint-recommended \ - @mermaid-js/mermaid-cli \ - graphviz \ - react \ - react-dom \ - react-icons \ - typescript \ - ts-node \ - tsx \ - sharp \ - playwright - -if [[ "$ARCH" == "x86_64" ]]; then - emit 04_npm progress "Installing markdown-pdf (x86_64 only)" - npm install -g markdown-pdf +if [[ "${OPENAGENT_USE_CN_MIRRORS:-0}" == "1" ]]; then + emit 04_npm progress "Configuring npm registry mirror" + npm config set registry "${OPENAGENT_NPM_REGISTRY}" >/dev/null 2>&1 || true fi + +emit 04_npm progress "Installing npm global packages" +npm install -g typescript tsx playwright diff --git a/libs/openagent/sandbox/vm/setup/steps/05_pip.sh b/libs/openagent/sandbox/vm/setup/steps/05_pip.sh index e0236d87..1960556a 100755 --- a/libs/openagent/sandbox/vm/setup/steps/05_pip.sh +++ b/libs/openagent/sandbox/vm/setup/steps/05_pip.sh @@ -1,64 +1,16 @@ #!/bin/bash -# Python packages (11 batches) -set -uo pipefail -# No -e: pip_install handles its own retries. +# Python packages (minimal baseline; install extras on demand) +set -euo pipefail -# Preflight: fix blinker conflict with system Flask -emit 05_pip progress "Preflight: fixing blinker" -pip3 install --break-system-packages --timeout 120 --ignore-installed blinker +emit 05_pip progress "Core data & visualization" +pip_install numpy pandas matplotlib pillow -emit 05_pip progress "Batch 1/11 — Core numeric (4 packages)" -pip_install numpy pandas scipy sympy +emit 05_pip progress "Web & HTTP" +pip_install requests beautifulsoup4 lxml -emit 05_pip progress "Batch 2/11 — ML / CV (3 packages)" -pip_install scikit-learn scikit-image onnxruntime - -emit 05_pip progress "Batch 3/11 — ML / CV OpenCV (3 packages)" -pip_install opencv-python opencv-contrib-python opencv-python-headless - -emit 05_pip progress "Batch 4/11 — Visualization (3 packages)" -pip_install matplotlib seaborn networkx - -emit 05_pip progress "Batch 5/11 — Image / media (5 packages)" -pip_install pillow imageio imageio-ffmpeg Wand pytesseract - -emit 05_pip progress "Batch 6/11 — PDF tools (11 packages)" -pip_install \ - pdfplumber pdfminer.six pypdf pikepdf pdf2image pdfkit \ - img2pdf camelot-py tabula-py reportlab pypdfium2 pymupdf - -emit 05_pip progress "Batch 7/11 — Office documents (5 packages)" -pip_install python-docx python-pptx openpyxl xlsxwriter odfpy - -emit 05_pip progress "Batch 8/11 — Markdown / docs (11 packages)" +emit 05_pip progress "Utilities" pip_install \ - markitdown markdownify markdown grip mistune markdown-it-py \ - marko mkdocs mkdocs-material mkdocs-material-extensions \ - mkdocs-get-deps pymdown-extensions - -emit 05_pip progress "Batch 9/11 — Web / HTTP (5 packages)" -pip_install requests beautifulsoup4 lxml Flask httplib2 - -emit 05_pip progress "Batch 10/11 — Automation / browser (3 packages)" -pip_install playwright unoserver pyoo - -emit 05_pip progress "Batch 11/11 — System utilities (14 packages)" -pip_install \ - uv magika click colorama coloredlogs humanfriendly tabulate \ - python-dotenv psutil watchdog sounddevice pycairo graphviz freetype-py - -# Foundational (many already installed as transitive deps — pip will no-op) -emit 05_pip progress "Foundational / low-level (17 packages)" -pip_install \ - attrs bcrypt jsonschema python-magic livereload tornado PyYAML \ - certifi charset-normalizer cryptography defusedxml idna joblib \ - packaging protobuf python-dateutil pytz typing_extensions urllib3 - -# Platform-specific -if [[ "$ARCH" == "x86_64" ]]; then - emit 05_pip progress "Platform-specific: mediapipe (x86_64 only)" - pip_install "mediapipe>=0.10.32" -fi + uv click pyyaml python-dotenv tabulate # Cleanup emit 05_pip progress "Cleaning pip cache" diff --git a/libs/openagent/sandbox/vm/setup/steps/06_playwright.sh b/libs/openagent/sandbox/vm/setup/steps/06_playwright.sh index f017ffbf..be4d13ac 100755 --- a/libs/openagent/sandbox/vm/setup/steps/06_playwright.sh +++ b/libs/openagent/sandbox/vm/setup/steps/06_playwright.sh @@ -6,11 +6,13 @@ set -euo pipefail dpkg --configure -a || true apt-get install -y -f || true -# Install Playwright OS deps (apt) — retry-wrapped +# Install Playwright OS deps (apt) retry-wrapped max_attempts=5 +deps_ok=0 for ((attempt = 1; attempt <= max_attempts; attempt++)); do emit 06_playwright progress "install-deps attempt $attempt/$max_attempts" if npx playwright install-deps chromium; then + deps_ok=1 break fi echo ">>> Retrying in 5s..." @@ -20,9 +22,41 @@ for ((attempt = 1; attempt <= max_attempts; attempt++)); do apt-get update || true done +if [[ $deps_ok -ne 1 ]]; then + emit 06_playwright error "Failed to install Playwright system dependencies" + exit 1 +fi + # Download Chromium binary emit 06_playwright progress "Downloading Chromium binary" -PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsers npx playwright install chromium +mirror_host="${OPENAGENT_PLAYWRIGHT_DOWNLOAD_HOST:-}" +browser_ok=0 +for ((attempt = 1; attempt <= max_attempts; attempt++)); do + if [[ "${OPENAGENT_USE_CN_MIRRORS:-0}" == "1" && -n "$mirror_host" ]]; then + emit 06_playwright progress "browser install attempt $attempt/$max_attempts (mirror)" + if PLAYWRIGHT_DOWNLOAD_HOST="$mirror_host" \ + PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsers \ + npx playwright install chromium; then + browser_ok=1 + break + fi + emit 06_playwright progress "Mirror unavailable, retrying with official host" + else + emit 06_playwright progress "browser install attempt $attempt/$max_attempts" + fi + + if PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsers npx playwright install chromium; then + browser_ok=1 + break + fi + echo ">>> Browser download attempt $attempt failed. Retrying in 5s..." + sleep 5 +done + +if [[ $browser_ok -ne 1 ]]; then + emit 06_playwright error "Failed to download Chromium browser" + exit 1 +fi # Allow PDF operations in ImageMagick (if restricted) if [[ -f /etc/ImageMagick-6/policy.xml ]] && \ diff --git a/libs/openagent_demo/backend/openagent_api/routes/setup.py b/libs/openagent_demo/backend/openagent_api/routes/setup.py index 2ce9d62e..6d754e7a 100644 --- a/libs/openagent_demo/backend/openagent_api/routes/setup.py +++ b/libs/openagent_demo/backend/openagent_api/routes/setup.py @@ -14,6 +14,7 @@ import os import platform import re +import shlex import shutil import subprocess as _sp import sys @@ -180,6 +181,10 @@ def _lima_status() -> dict[str, object]: _WSL_INSTANCE = "openagent" _WSL_EXPORT_SOURCE = "Ubuntu" +_WSL_PREBUILT_CANDIDATES = ( + "openagent-prebuilt.tar", + "openagent.tar", +) def _wsl_cmd() -> str | None: @@ -208,6 +213,68 @@ def _decode_wsl_output(raw: bytes) -> str: return raw.decode("utf-8", errors="replace") +def _combine_wsl_output(stdout_b: bytes | None, stderr_b: bytes | None) -> str: + """Decode and combine WSL stdout/stderr, preferring non-empty stderr first.""" + err = _decode_wsl_output(stderr_b or b"").strip() + out = _decode_wsl_output(stdout_b or b"").strip() + if err and out: + return f"{err}\n{out}" + return err or out + + +def _looks_like_missing_wsl_disk(msg: str) -> bool: + text = msg.lower() + return ( + "error_path_not_found" in text + or "mountdisk" in text + or "ext4.vhdx" in text + ) + + +def _wsl2_blocker_reason(text: str) -> str | None: + """Return a friendly reason when host cannot run WSL2.""" + t = (text or "").lower() + blockers = ( + "does not support wsl2", + "not support wsl2", + "wsl2", + "enablevirtualization", + "virtual machine platform", + "bios", + "当前计算机配置不支持 wsl2", + "虚拟机平台", + ) + if any(k in t for k in blockers): + return ( + "WSL2 is not available on this PC yet. Please enable " + "'Virtual Machine Platform', ensure virtualization is enabled in BIOS, " + "then reboot Windows and retry." + ) + return None + + +def _probe_wsl2_readiness() -> tuple[bool, str | None]: + """Check whether host is ready for WSL2-based distro import/start.""" + wsl = _wsl_cmd() + if not wsl: + return False, "wsl.exe not found" + try: + proc = _sp.run( + [wsl, "--status"], + stdout=_sp.PIPE, + stderr=_sp.PIPE, + timeout=8, + ) + except Exception as exc: # pragma: no cover - defensive + return False, str(exc) + + combined = _combine_wsl_output(proc.stdout, proc.stderr).strip() + reason = _wsl2_blocker_reason(combined) + if reason: + return False, reason + return True, None + + def _parse_wsl_list(text: str) -> list[dict[str, str]]: entries: list[dict[str, str]] = [] for line in text.splitlines(): @@ -219,9 +286,13 @@ def _parse_wsl_list(text: str) -> list[dict[str, str]]: if stripped.startswith("*"): stripped = stripped[1:].strip() parts = stripped.split() - if len(parts) < 3: + if len(parts) >= 3: + entries.append({"name": parts[0], "state": parts[1], "version": parts[2]}) continue - entries.append({"name": parts[0], "state": parts[1], "version": parts[2]}) + # Older WSL builds may only return distro names with `wsl --list`. + # Keep them with a synthetic state so downstream logic can still detect existence. + if len(parts) == 1 and parts[0].lower() not in {"windows", "subsystem", "linux"}: + entries.append({"name": parts[0], "state": "Unknown", "version": ""}) return entries @@ -229,17 +300,27 @@ async def _wsl_list() -> list[dict[str, str]]: wsl_exe = _wsl_cmd() if not wsl_exe: return [] - proc = await asyncio.create_subprocess_exec( - wsl_exe, - "--list", - "--verbose", - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, + # Newer WSL supports `--list --verbose`, while older builds support `-l -v` + # or only plain `--list`. Try all variants for best compatibility. + variants = ( + ("--list", "--verbose"), + ("-l", "-v"), + ("--list",), ) - out_b, _ = await proc.communicate() - if proc.returncode != 0: - return [] - return _parse_wsl_list(_decode_wsl_output(out_b or b"")) + for args in variants: + proc = await asyncio.create_subprocess_exec( + wsl_exe, + *args, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + out_b, _err_b = await proc.communicate() + if proc.returncode != 0: + continue + parsed = _parse_wsl_list(_decode_wsl_output(out_b or b"")) + if parsed: + return parsed + return [] async def _wsl_instance_status() -> str | None: @@ -250,6 +331,46 @@ async def _wsl_instance_status() -> str | None: return None +def _wsl_prebuilt_tar_path() -> Path | None: + """Return bundled prebuilt WSL rootfs tar if present.""" + prebuilt_dir = vm_setup_dir().parent / "wsl" / "prebuilt" + for name in _WSL_PREBUILT_CANDIDATES: + candidate = prebuilt_dir / name + if candidate.is_file(): + return candidate + return None + + +async def _wsl_probe_start() -> tuple[bool, str]: + """Best-effort probe that distro can actually start. + + This catches cases where `wsl -l -v` still lists the distro (Stopped), + but its backing VHDX path is missing/corrupted. + """ + wsl_exe = _wsl_cmd() + if not wsl_exe: + return False, "wsl.exe not found" + proc = await asyncio.create_subprocess_exec( + wsl_exe, + "-d", + _WSL_INSTANCE, + "--", + "echo", + "ok", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + try: + stdout_b, stderr_b = await asyncio.wait_for(proc.communicate(), timeout=20) + except asyncio.TimeoutError: + proc.kill() + await proc.wait() + return False, "WSL start probe timed out" + if (proc.returncode or 0) == 0: + return True, "" + return False, _combine_wsl_output(stdout_b, stderr_b) + + # ``wsl -l -v`` uses the Windows display language for the STATE column. # Cowork only needs the ``openagent`` distro to exist; WSL starts it on demand. _WSL_COWORK_READY_STATES = frozenset( @@ -305,7 +426,49 @@ def _pick_wsl_source_distro(entries: list[dict[str, str]]) -> str | None: def _wsl_status() -> dict[str, object]: wsl = _wsl_cmd() - return {"installed": bool(wsl), "path": wsl, "managed": False} + if not wsl: + return {"installed": False, "path": None, "managed": False} + + ready, reason = _probe_wsl2_readiness() + if not ready: + return { + "installed": False, + "path": wsl, + "managed": False, + "reason": reason or "WSL runtime is not available", + } + + # `wsl.exe` may exist even when WSL optional components are not enabled. + # Probe command success instead of relying on binary presence. + probe_variants = ( + ("--status",), + ("--list", "--verbose"), + ("-l", "-v"), + ("--list",), + ) + last_err = "" + for args in probe_variants: + try: + proc = _sp.run( + [wsl, *args], + stdout=_sp.PIPE, + stderr=_sp.PIPE, + timeout=8, + ) + except Exception as exc: # pragma: no cover - defensive + last_err = str(exc) + continue + + if proc.returncode == 0: + return {"installed": True, "path": wsl, "managed": False} + last_err = _combine_wsl_output(proc.stdout, proc.stderr).strip() or last_err + + return { + "installed": False, + "path": wsl, + "managed": False, + "reason": last_err or "WSL runtime is not available", + } def _win_path_to_wsl(path: Path | str) -> str: @@ -492,6 +655,7 @@ async def get_vm_status() -> dict[str, object]: vm_ready = False instance_status: str | None = None + instance_error: str | None = None if result.get("installed"): try: if result.get("backend") == "lima": @@ -500,10 +664,17 @@ async def get_vm_status() -> dict[str, object]: elif result.get("backend") == "wsl": instance_status = await _wsl_instance_status() vm_ready = _wsl_distro_ready_for_cowork(instance_status) + if vm_ready: + ok, err = await _wsl_probe_start() + if not ok: + vm_ready = False + instance_error = err or "WSL distro exists but failed to start" except Exception: pass result["instance_status"] = instance_status + if instance_error: + result["instance_error"] = instance_error result["vm_ready"] = vm_ready return result @@ -772,6 +943,13 @@ async def _run_wsl(self) -> None: self._error = "WSL missing" return + ready, reason = _probe_wsl2_readiness() + if not ready: + self._emit("error", {"message": reason or "WSL2 runtime is not ready"}) + self._status = "error" + self._error = "WSL2 not ready" + return + status = await _wsl_instance_status() if _wsl_state_equals(status, _WSL_RUNNING_STATES): self._emit("done", {"message": "WSL distro is already running"}) @@ -786,7 +964,7 @@ async def _run_wsl(self) -> None: stderr=asyncio.subprocess.PIPE, ) self._process = proc - _, stderr_b = await self._communicate_with_heartbeat( + stdout_b, stderr_b = await self._communicate_with_heartbeat( proc, step="starting", message="Starting existing WSL distro...", @@ -794,14 +972,87 @@ async def _run_wsl(self) -> None: if proc.returncode == 0: self._emit("done", {"message": "WSL distro started successfully"}) self._status = "done" + return else: - err = _decode_wsl_output(stderr_b or b"").strip() - self._emit("error", {"message": err or f"WSL start failed (exit {proc.returncode})"}) + err = _combine_wsl_output(stdout_b, stderr_b) + if _looks_like_missing_wsl_disk(err): + self._emit("progress", {"step": "creating", "message": "Detected broken WSL distro disk. Recreating OpenAgent distro..."}) + proc_unreg = await asyncio.create_subprocess_exec( + wsl_exe, "--unregister", _WSL_INSTANCE, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + self._process = proc_unreg + u_out_b, u_err_b = await self._communicate_with_heartbeat( + proc_unreg, + step="creating", + message="Removing broken OpenAgent WSL distro...", + ) + if proc_unreg.returncode != 0: + u_err = _combine_wsl_output(u_out_b, u_err_b) + self._emit("error", {"message": u_err or f"WSL unregister failed (exit {proc_unreg.returncode})"}) + self._status = "error" + self._error = f"exit {proc_unreg.returncode}" + return + # Continue with fresh-create flow below. + else: + self._emit("error", {"message": err or f"WSL start failed (exit {proc.returncode})"}) + self._status = "error" + self._error = f"exit {proc.returncode}" + return + + prebuilt_tar = _wsl_prebuilt_tar_path() + import_dir = data_dir() / "wsl" / _WSL_INSTANCE / "disk" + + # Distro does not exist: prefer bundled prebuilt OpenAgent rootfs. + if prebuilt_tar is not None: + self._emit("progress", {"step": "creating", "message": "Importing bundled OpenAgent VM image..."}) + if import_dir.exists(): + shutil.rmtree(import_dir, ignore_errors=True) + import_dir.mkdir(parents=True, exist_ok=True) + + proc_import = await asyncio.create_subprocess_exec( + wsl_exe, "--import", _WSL_INSTANCE, str(import_dir), str(prebuilt_tar), "--version", "2", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + self._process = proc_import + _, err_b = await self._communicate_with_heartbeat( + proc_import, + step="creating", + message="Importing bundled OpenAgent VM image...", + progress_info=lambda: f"(image ~{(prebuilt_tar.stat().st_size / (1024 * 1024)):.1f} MB)", + ) + if proc_import.returncode != 0: + err = _decode_wsl_output(err_b or b"").strip() + self._emit("error", {"message": err or f"Bundled image import failed (exit {proc_import.returncode})"}) self._status = "error" - self._error = f"exit {proc.returncode}" + self._error = f"exit {proc_import.returncode}" + return + + self._emit("progress", {"step": "starting", "message": "Starting imported OpenAgent WSL distro..."}) + proc_start = await asyncio.create_subprocess_exec( + wsl_exe, "-d", _WSL_INSTANCE, "--", "echo", "ok", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + self._process = proc_start + out_b, err_b = await self._communicate_with_heartbeat( + proc_start, + step="starting", + message="Starting imported OpenAgent WSL distro...", + ) + if proc_start.returncode == 0: + self._emit("done", {"message": "WSL distro imported from bundled image and started successfully"}) + self._status = "done" + else: + err = _combine_wsl_output(out_b, err_b) + self._emit("error", {"message": err or f"WSL start failed (exit {proc_start.returncode})"}) + self._status = "error" + self._error = f"exit {proc_start.returncode}" return - # Distro does not exist: bootstrap from Ubuntu export. + # Fallback: bootstrap from Ubuntu export. self._emit("progress", {"step": "creating", "message": "Preparing source distro (Ubuntu)..."}) entries = await _wsl_list() source_distro = _pick_wsl_source_distro(entries) @@ -841,7 +1092,6 @@ async def _run_wsl(self) -> None: export_root = deps_dir() / "wsl" export_root.mkdir(parents=True, exist_ok=True) export_tar = export_root / f"{source_distro.lower()}-seed.tar" - import_dir = data_dir() / "wsl" / _WSL_INSTANCE / "disk" import_dir.mkdir(parents=True, exist_ok=True) self._emit("progress", {"step": "creating", "message": "Exporting Ubuntu rootfs..."}) @@ -890,7 +1140,7 @@ async def _run_wsl(self) -> None: stderr=asyncio.subprocess.PIPE, ) self._process = proc_start - _, err_b = await self._communicate_with_heartbeat( + out_b, err_b = await self._communicate_with_heartbeat( proc_start, step="starting", message="Starting OpenAgent WSL distro...", @@ -899,7 +1149,7 @@ async def _run_wsl(self) -> None: self._emit("done", {"message": "WSL distro created and started successfully"}) self._status = "done" else: - err = _decode_wsl_output(err_b or b"").strip() + err = _combine_wsl_output(out_b, err_b) self._emit("error", {"message": err or f"WSL start failed (exit {proc_start.returncode})"}) self._status = "error" self._error = f"exit {proc_start.returncode}" @@ -1051,11 +1301,34 @@ async def _run_lima(self, **kwargs: object) -> None: async def _run_wsl(self, **kwargs: object) -> None: force = bool(kwargs.get("force", False)) instance_status = await _wsl_instance_status() - if instance_status != "Running": - self._emit("error", {"message": f"WSL distro is not running (status: {instance_status})"}) + # Keep cowork behavior consistent with /api/setup/vm: + # distro may be Stopped but still ready; start it on-demand. + if not _wsl_distro_ready_for_cowork(instance_status): + self._emit("error", {"message": f"WSL distro is not available (status: {instance_status})"}) self._status = "error" - self._error = "WSL distro not running" + self._error = "WSL distro unavailable" return + if not _wsl_state_equals(instance_status, _WSL_RUNNING_STATES): + self._emit("progress", {"step": "starting", "message": "Starting WSL distro for provisioning..."}) + wsl_exe = _wsl_cmd() + if not wsl_exe: + self._emit("error", {"message": "wsl.exe not found - cannot provision"}) + self._status = "error" + self._error = "WSL missing" + return + proc_start = await asyncio.create_subprocess_exec( + wsl_exe, "-d", _WSL_INSTANCE, "--", "echo", "ok", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + self._process = proc_start + stdout_b, stderr_b = await proc_start.communicate() + if proc_start.returncode != 0: + err = _combine_wsl_output(stdout_b, stderr_b) + self._emit("error", {"message": err or f"WSL start failed (exit {proc_start.returncode})"}) + self._status = "error" + self._error = f"exit {proc_start.returncode}" + return self._emit("progress", {"step": "copying", "message": "Preparing setup files in WSL..."}) setup_dir = vm_setup_dir() @@ -1066,9 +1339,11 @@ async def _run_wsl(self, **kwargs: object) -> None: return setup_wsl = _win_path_to_wsl(setup_dir) + setup_wsl_quoted = shlex.quote(setup_wsl) + setup_vm_dir_quoted = shlex.quote(_SETUP_VM_DIR) rc, _, err = await _wsl_shell( - f"sudo rm -rf {_SETUP_VM_DIR} && sudo mkdir -p {_SETUP_VM_DIR} && " - f"sudo cp -r {setup_wsl}/. {_SETUP_VM_DIR}/", + f"sudo rm -rf {setup_vm_dir_quoted} && sudo mkdir -p {setup_vm_dir_quoted} && " + f"sudo cp -r {setup_wsl_quoted}/. {setup_vm_dir_quoted}/", timeout=60, ) if rc != 0: diff --git a/libs/openagent_demo/electron/main.js b/libs/openagent_demo/electron/main.js index 7591777d..2addbd09 100644 --- a/libs/openagent_demo/electron/main.js +++ b/libs/openagent_demo/electron/main.js @@ -195,12 +195,78 @@ function killBackend() { } } +function runCommand(cmd, args) { + return new Promise((resolve) => { + const p = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] }); + let stdout = ""; + let stderr = ""; + p.stdout?.on("data", (d) => { stdout += d.toString(); }); + p.stderr?.on("data", (d) => { stderr += d.toString(); }); + p.on("error", (err) => { + resolve({ code: 1, stdout, stderr: `${stderr}\n${err.message}`.trim() }); + }); + p.on("close", (code) => { + resolve({ code: code ?? 1, stdout, stderr }); + }); + }); +} + // ── IPC ────────────────────────────────────────────────────────────────────── ipcMain.on("get-backend-port", (event) => { event.returnValue = backendPort; }); +ipcMain.handle("install-wsl-runtime", async () => { + if (process.platform !== "win32") { + return { ok: false, message: "This action is only available on Windows." }; + } + + // Launch WSL installation with UAC elevation so non-technical users can + // complete prerequisites in-app with one click. + const psScript = ` +$ErrorActionPreference = 'Stop' +$proc = Start-Process -FilePath "wsl.exe" -ArgumentList "--install","--no-distribution" -Verb RunAs -Wait -PassThru +if ($null -eq $proc) { exit 1 } +exit $proc.ExitCode +`.trim(); + + const res = await runCommand("powershell.exe", [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + psScript, + ]); + + // WSL optional features often need a reboot before WSL2 import/start works. + // We gate follow-up VM instance setup on reboot to avoid first-run import failures. + const success = res.code === 0 || res.code === 3010; + const rebootRequired = success; + if (success) { + return { + ok: true, + rebootRequired, + exitCode: res.code, + message: "Runtime installation completed. Please restart Windows before continuing VM setup.", + stdout: res.stdout, + stderr: res.stderr, + }; + } + + const combined = `${res.stderr || ""}\n${res.stdout || ""}`.trim(); + const cancelled = /canceled|cancelled|拒绝|已取消|denied/i.test(combined); + if (cancelled) { + return { ok: false, exitCode: res.code, message: "Installation was cancelled." }; + } + + return { + ok: false, + exitCode: res.code, + message: combined || `Runtime installation failed (exit ${res.code}).`, + }; +}); + // ── Window ─────────────────────────────────────────────────────────────────── function createWindow() { diff --git a/libs/openagent_demo/electron/preload.js b/libs/openagent_demo/electron/preload.js index 8e1a6251..cb15a785 100644 --- a/libs/openagent_demo/electron/preload.js +++ b/libs/openagent_demo/electron/preload.js @@ -5,4 +5,5 @@ contextBridge.exposeInMainWorld("electronAPI", { backendPort: ipcRenderer.sendSync("get-backend-port"), isElectron: true, platform: process.platform, + installWslRuntime: () => ipcRenderer.invoke("install-wsl-runtime"), }); diff --git a/libs/openagent_demo/electron/scripts/build-all.ps1 b/libs/openagent_demo/electron/scripts/build-all.ps1 index 2fd33da8..032fc487 100644 --- a/libs/openagent_demo/electron/scripts/build-all.ps1 +++ b/libs/openagent_demo/electron/scripts/build-all.ps1 @@ -3,6 +3,7 @@ $ErrorActionPreference = 'Stop' $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $ElectronDir = Resolve-Path "$ScriptDir\.." $Target = if ($args.Count -gt 0) { $args[0] } else { 'win' } +$EmbedWslPrebuilt = ($env:OPENAGENT_EMBED_WSL_PREBUILT -eq "1") Write-Host '=========================================' Write-Host ' OpenAgent Desktop - Build ('$Target')' @@ -24,6 +25,12 @@ Write-Host '' Write-Host '[2/3] Skipping electron dependencies (already installed)...' Set-Location $ElectronDir +if ($Target -eq 'win' -and $EmbedWslPrebuilt) { + Write-Host '' + Write-Host '[2.2/3] Exporting prebuilt WSL VM image for offline-ready package...' + & "$ScriptDir\prepare-wsl-prebuilt.ps1" +} + Write-Host '' Write-Host '[2.5/3] Building backend...' # Call PowerShell version of backend build script @@ -44,4 +51,4 @@ Write-Host ' Build complete! Output in dist/' Write-Host '=========================================' # List build artifacts -Get-ChildItem "$ElectronDir\dist\*.exe", "$ElectronDir\dist\*.blockmap" | Format-Table -AutoSize \ No newline at end of file +Get-ChildItem "$ElectronDir\dist\*.exe", "$ElectronDir\dist\*.blockmap" | Format-Table -AutoSize diff --git a/libs/openagent_demo/electron/scripts/build-backend.ps1 b/libs/openagent_demo/electron/scripts/build-backend.ps1 index c4e64c03..d86f3a56 100644 --- a/libs/openagent_demo/electron/scripts/build-backend.ps1 +++ b/libs/openagent_demo/electron/scripts/build-backend.ps1 @@ -6,34 +6,46 @@ $BackendDir = Resolve-Path "$ElectronDir\..\backend" Write-Host "==> Installing PyInstaller..." Set-Location $BackendDir -uv pip install pyinstaller +$pyinstallerArgs = @( + "--name", "openagent_api_server", + "--onedir", + "--noconfirm", + "--hidden-import", "uvicorn.logging", + "--hidden-import", "uvicorn.loops", + "--hidden-import", "uvicorn.loops.auto", + "--hidden-import", "uvicorn.loops.asyncio", + "--hidden-import", "uvicorn.protocols", + "--hidden-import", "uvicorn.protocols.http", + "--hidden-import", "uvicorn.protocols.http.auto", + "--hidden-import", "uvicorn.protocols.http.h11_impl", + "--hidden-import", "uvicorn.protocols.http.httptools_impl", + "--hidden-import", "uvicorn.protocols.websockets", + "--hidden-import", "uvicorn.protocols.websockets.auto", + "--hidden-import", "uvicorn.protocols.websockets.wsproto_impl", + "--hidden-import", "uvicorn.protocols.websockets.websockets_impl", + "--hidden-import", "uvicorn.lifespan", + "--hidden-import", "uvicorn.lifespan.on", + "--hidden-import", "uvicorn.lifespan.off", + "--collect-submodules", "openagent_api", + "--collect-submodules", "openagent", + "--collect-data", "openagent", + "--add-data", "../../openagent/sandbox/vm;sandbox/vm", + "--add-data", "skills;skills", + "openagent_api/server.py" +) -Write-Host "==> Building backend with PyInstaller..." -uv run pyinstaller ` - --name openagent_api_server ` - --onedir ` - --noconfirm ` - --hidden-import uvicorn.logging ` - --hidden-import uvicorn.loops ` - --hidden-import uvicorn.loops.auto ` - --hidden-import uvicorn.loops.asyncio ` - --hidden-import uvicorn.protocols ` - --hidden-import uvicorn.protocols.http ` - --hidden-import uvicorn.protocols.http.auto ` - --hidden-import uvicorn.protocols.http.h11_impl ` - --hidden-import uvicorn.protocols.http.httptools_impl ` - --hidden-import uvicorn.protocols.websockets ` - --hidden-import uvicorn.protocols.websockets.auto ` - --hidden-import uvicorn.protocols.websockets.wsproto_impl ` - --hidden-import uvicorn.protocols.websockets.websockets_impl ` - --hidden-import uvicorn.lifespan ` - --hidden-import uvicorn.lifespan.on ` - --hidden-import uvicorn.lifespan.off ` - --collect-submodules openagent_api ` - --collect-submodules openagent ` - --collect-data openagent ` - --add-data "skills;skills" ` - openagent_api/server.py +if (Get-Command uv -ErrorAction SilentlyContinue) { + uv pip install pyinstaller + Write-Host "==> Building backend with PyInstaller (uv)..." + uv run pyinstaller @pyinstallerArgs +} else { + $venvPython = Join-Path $BackendDir ".venv\Scripts\python.exe" + if (-not (Test-Path $venvPython)) { + throw "uv not found and backend venv python missing: $venvPython" + } + Write-Host "==> uv not found, using backend venv python fallback..." + & $venvPython -m PyInstaller @pyinstallerArgs +} Write-Host "==> Copying dist to electron/backend_dist..." if (Test-Path "$ElectronDir\backend_dist") { diff --git a/libs/openagent_demo/electron/scripts/prepare-wsl-prebuilt.ps1 b/libs/openagent_demo/electron/scripts/prepare-wsl-prebuilt.ps1 new file mode 100644 index 00000000..2f551f94 --- /dev/null +++ b/libs/openagent_demo/electron/scripts/prepare-wsl-prebuilt.ps1 @@ -0,0 +1,37 @@ +$ErrorActionPreference = "Stop" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$OpenagentRoot = Resolve-Path "$ScriptDir\..\..\.." +$PrebuiltDir = Join-Path $OpenagentRoot "openagent\sandbox\vm\wsl\prebuilt" +$PrebuiltTar = Join-Path $PrebuiltDir "openagent-prebuilt.tar" +$DistroName = "openagent" + +if ($env:OS -ne "Windows_NT") { + Write-Host "Skipping WSL prebuilt export: non-Windows environment." + exit 0 +} + +if (-not (Get-Command wsl -ErrorAction SilentlyContinue)) { + throw "wsl command not found. Install WSL first." +} + +Write-Host "==> Ensuring distro '$DistroName' can start..." +& wsl -d $DistroName -- echo ok | Out-Null +if ($LASTEXITCODE -ne 0) { + throw "WSL distro '$DistroName' is not available/runnable. Please initialize VM Instance first." +} + +New-Item -ItemType Directory -Force -Path $PrebuiltDir | Out-Null +if (Test-Path $PrebuiltTar) { + Remove-Item -Force $PrebuiltTar +} + +Write-Host "==> Exporting '$DistroName' to $PrebuiltTar (this can take several minutes)..." +wsl --export $DistroName $PrebuiltTar + +if (-not (Test-Path $PrebuiltTar)) { + throw "WSL export completed but output tar was not found: $PrebuiltTar" +} + +$sizeMb = [math]::Round(((Get-Item $PrebuiltTar).Length / 1MB), 1) +Write-Host "==> WSL prebuilt image ready: $PrebuiltTar (${sizeMb} MB)" diff --git a/libs/openagent_demo/frontend/src/components/SettingsModal.tsx b/libs/openagent_demo/frontend/src/components/SettingsModal.tsx index 31e4fb22..ec6e9d63 100644 --- a/libs/openagent_demo/frontend/src/components/SettingsModal.tsx +++ b/libs/openagent_demo/frontend/src/components/SettingsModal.tsx @@ -1739,6 +1739,7 @@ function SandboxTab({ config, onConfigChange }: ConfigTabProps) { const allDone = vmUsable && phase3 === "done"; const anyRunning = phase1 === "running" || phase2 === "running" || phase3 === "running"; const coreError = phase1 === "error" || phase2 === "error"; + const phase1NeedsRestart = /restart windows|重启.*windows|重启.*电脑|reboot/i.test(phase1Error || ""); return (
@@ -1837,10 +1838,20 @@ function SandboxTab({ config, onConfigChange }: ConfigTabProps) { {phase1 === "done" && Installed} {phase1 === "running" && phase1Msg && {phase1Msg}} {phase1 === "pending" && ( - + )} {phase1 === "error" && ( - + phase1NeedsRestart ? ( + + ) : ( + + ) )}
{phase1 === "error" && phase1Error && ( diff --git a/libs/openagent_demo/frontend/src/electron.d.ts b/libs/openagent_demo/frontend/src/electron.d.ts index 2b7220e4..61b88d74 100644 --- a/libs/openagent_demo/frontend/src/electron.d.ts +++ b/libs/openagent_demo/frontend/src/electron.d.ts @@ -4,6 +4,16 @@ declare global { interface Window { electronAPI?: { backendPort?: number; + isElectron?: boolean; + platform?: string; + installWslRuntime?: () => Promise<{ + ok: boolean; + rebootRequired?: boolean; + exitCode?: number; + message?: string; + stdout?: string; + stderr?: string; + }>; }; } } diff --git a/libs/openagent_demo/frontend/src/vmSetup.tsx b/libs/openagent_demo/frontend/src/vmSetup.tsx index a91e5860..b4b3c7f9 100644 --- a/libs/openagent_demo/frontend/src/vmSetup.tsx +++ b/libs/openagent_demo/frontend/src/vmSetup.tsx @@ -50,6 +50,7 @@ export interface VMSetupContextValue { provLog: string | null; installLima: () => void; + recheckVmEngine: () => void; buildVMInstance: () => void; startProvision: (force?: boolean) => void; stopProvision: () => void; @@ -107,6 +108,42 @@ export function VMSetupProvider({ children }: { children: ReactNode }) { dispatch({ type: "SHOW_NOTIFICATION", payload: { message, type } }); }; + const doRecheckVmEngine = async () => { + setPhase1("checking"); + setPhase1Msg("Re-checking runtime status..."); + setPhase1Error(""); + try { + const vs = await getVMStatus(); + setVmStatus(vs); + dispatch({ type: "SET_VM_STATUS", payload: vs }); + if (!vs.supported) { + setPhase1("error"); + setPhase1Msg(""); + setPhase1Error("Not supported on this platform"); + return; + } + if (vs.installed) { + setPhase1("done"); + setPhase1Msg(""); + setPhase1Error(""); + setPhase2("pending"); + attachBuild(); + } else if (vs.reason) { + setPhase1("error"); + setPhase1Msg(""); + setPhase1Error(vs.reason); + } else { + setPhase1("pending"); + setPhase1Msg(""); + setPhase1Error(""); + } + } catch { + setPhase1("error"); + setPhase1Msg(""); + setPhase1Error("Could not check VM status"); + } + }; + // ── Phase 3: Provision ── const attachProvision = (force = false) => { @@ -185,6 +222,63 @@ export function VMSetupProvider({ children }: { children: ReactNode }) { // ── Phase 1: Install Lima ── const doInstallLima = () => { + const inferredBackend = navigator.platform.toUpperCase().includes("WIN") ? "wsl" : "lima"; + const currentBackend = vmStatus?.backend ?? inferredBackend; + const canAssistWslInstall = + currentBackend === "wsl" && + typeof window !== "undefined" && + typeof window.electronAPI?.installWslRuntime === "function"; + + if (canAssistWslInstall) { + setPhase1("running"); + setPhase1Error(""); + setPhase1Msg("Installing required Windows runtime (admin permission needed)..."); + + window.electronAPI! + .installWslRuntime!() + .then(async (res) => { + if (!res.ok) { + const msg = res.message || "Runtime installation failed."; + setPhase1("error"); + setPhase1Error(msg); + notify(msg, "error"); + return; + } + + if (res.rebootRequired) { + const msg = res.message || "Runtime installed. Please restart Windows before continuing VM setup."; + setPhase1("error"); + setPhase1Msg(""); + setPhase1Error(msg); + notify(msg, "info"); + return; + } + + const vs = await getVMStatus(); + setVmStatus(vs); + dispatch({ type: "SET_VM_STATUS", payload: vs }); + + if (vs.installed) { + setPhase1("done"); + setPhase1Msg(""); + notify("Runtime installed", "success"); + setPhase2("pending"); + attachBuild(); + } else { + setPhase1("pending"); + setPhase1Msg("Runtime install command completed. Click Retry if needed."); + notify("Runtime install command completed", "info"); + } + }) + .catch((err: unknown) => { + const msg = err instanceof Error ? err.message : String(err); + setPhase1("error"); + setPhase1Error(msg); + notify(`Runtime installation failed: ${msg}`, "error"); + }); + return; + } + setPhase1("running"); setPhase1Error(""); setPhase1Msg("Starting installation..."); @@ -266,6 +360,9 @@ export function VMSetupProvider({ children }: { children: ReactNode }) { if (vs.installed) { setPhase1("done"); limaInstalled = true; + } else if (vs.reason) { + setPhase1("error"); + setPhase1Error(vs.reason); } else { setPhase1("pending"); } @@ -351,6 +448,7 @@ export function VMSetupProvider({ children }: { children: ReactNode }) { phase3, phase3Error, provSteps, provStepStatus, provStepMsg, provLog, installLima: doInstallLima, + recheckVmEngine: doRecheckVmEngine, buildVMInstance: attachBuild, startProvision: doStartProvision, stopProvision: doStopProvision, From 47501c9e108807ce1a2a9cd2be93dde3e7074750 Mon Sep 17 00:00:00 2001 From: xuelin-cell Date: Thu, 26 Mar 2026 19:02:47 +0800 Subject: [PATCH 08/28] fix(vm-win): harden WSL startup/user creation and add regressions --- .../openagent/computer/local/_wsl.py | 30 ++++++++---- .../openagent/computer/local/vm_win.py | 20 +++++++- .../sandbox/vm/setup/steps/06_playwright.sh | 13 ++++- .../unit_tests/computer/test_local_vm_win.py | 30 ++++++++++-- .../tests/unit_tests/computer/test_wsl.py | 47 +++++++++++++++++++ 5 files changed, 123 insertions(+), 17 deletions(-) diff --git a/libs/openagent/openagent/computer/local/_wsl.py b/libs/openagent/openagent/computer/local/_wsl.py index 5708c81d..483886a1 100644 --- a/libs/openagent/openagent/computer/local/_wsl.py +++ b/libs/openagent/openagent/computer/local/_wsl.py @@ -312,15 +312,27 @@ async def start(self) -> None: if current != "Running": # Trigger start by running a trivial command. - await self._run_wsl( - self._wsl_exe, - "-d", - self._instance, - "--", - "echo", - "ok", - timeout=120, - ) + # Some Windows hosts occasionally return a transient -1/4294967295 + # from wsl.exe during startup even though a subsequent attempt works. + for attempt in range(2): + try: + await self._run_wsl( + self._wsl_exe, + "-d", + self._instance, + "--", + "echo", + "ok", + timeout=120, + ) + break + except WslError as exc: + text = str(exc).lower() + transient = "exit 4294967295" in text or "exit -1" in text + if transient and attempt == 0: + await asyncio.sleep(0.5) + continue + raise await self._apply_bind_mounts() diff --git a/libs/openagent/openagent/computer/local/vm_win.py b/libs/openagent/openagent/computer/local/vm_win.py index dd15b5e2..cc0485fa 100644 --- a/libs/openagent/openagent/computer/local/vm_win.py +++ b/libs/openagent/openagent/computer/local/vm_win.py @@ -602,14 +602,30 @@ async def _create_user(self, name: str) -> None: qname = shlex.quote(name) home = f"/sessions/{name}" qhome = shlex.quote(home) - result = await self._vm.shell(f"sudo useradd -m -d {qhome} -s /bin/bash --no-log-init -K SUB_UID_COUNT=0 -K SUB_GID_COUNT=0 {qname}") + sudo_probe = await self._vm.shell("command -v sudo >/dev/null 2>&1") + sudo_prefix = "sudo " if sudo_probe.exit_code == 0 else "" + + create_cmd = ( + f"{sudo_prefix}useradd -m -d {qhome} -s /bin/bash " + f"--no-log-init -K SUB_UID_COUNT=0 -K SUB_GID_COUNT=0 {qname}" + ) + result = await self._vm.shell(create_cmd) + if result.exit_code != 0: + # Some Ubuntu/WSL images reject useradd with SUB_UID/GID_COUNT=0. + # Retry without those overrides for compatibility. We retry + # regardless of locale-specific stderr text. + fallback_cmd = ( + f"{sudo_prefix}useradd -m -d {qhome} -s /bin/bash " + f"--no-log-init {qname}" + ) + result = await self._vm.shell(fallback_cmd) if result.exit_code != 0: msg = f"Failed to create session user '{name}': {result.stderr}" raise VMError(msg) all_dirs = " ".join(shlex.quote(f"{home}/{d}") for d in SESSION_DIRS) writable = f"{shlex.quote(f'{home}/{SESSION_TMP_DIR}')} {shlex.quote(f'{home}/{SESSION_OUTPUTS_DIR}')}" - result = await self._vm.shell(f"sudo mkdir -p {all_dirs} && sudo chown {qname} {writable}") + result = await self._vm.shell(f"{sudo_prefix}mkdir -p {all_dirs} && {sudo_prefix}chown {qname} {writable}") if result.exit_code != 0: msg = f"Failed to create session directories for '{name}': {result.stderr}" raise VMError(msg) diff --git a/libs/openagent/sandbox/vm/setup/steps/06_playwright.sh b/libs/openagent/sandbox/vm/setup/steps/06_playwright.sh index be4d13ac..c1b344b8 100755 --- a/libs/openagent/sandbox/vm/setup/steps/06_playwright.sh +++ b/libs/openagent/sandbox/vm/setup/steps/06_playwright.sh @@ -31,7 +31,18 @@ fi emit 06_playwright progress "Downloading Chromium binary" mirror_host="${OPENAGENT_PLAYWRIGHT_DOWNLOAD_HOST:-}" browser_ok=0 + +# If bundled browsers are already present in the VM image, skip network download. +if find /opt/pw-browsers -type f \( -name "chrome-headless-shell" -o -name "chrome" \) 2>/dev/null | grep -q .; then + emit 06_playwright progress "Bundled Playwright browser detected under /opt/pw-browsers, skipping download" + browser_ok=1 +fi + for ((attempt = 1; attempt <= max_attempts; attempt++)); do + if [[ $browser_ok -eq 1 ]]; then + break + fi + if [[ "${OPENAGENT_USE_CN_MIRRORS:-0}" == "1" && -n "$mirror_host" ]]; then emit 06_playwright progress "browser install attempt $attempt/$max_attempts (mirror)" if PLAYWRIGHT_DOWNLOAD_HOST="$mirror_host" \ @@ -45,7 +56,7 @@ for ((attempt = 1; attempt <= max_attempts; attempt++)); do emit 06_playwright progress "browser install attempt $attempt/$max_attempts" fi - if PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsers npx playwright install chromium; then + if env -u PLAYWRIGHT_DOWNLOAD_HOST PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsers npx playwright install chromium; then browser_ok=1 break fi diff --git a/libs/openagent/tests/unit_tests/computer/test_local_vm_win.py b/libs/openagent/tests/unit_tests/computer/test_local_vm_win.py index 2543c3c4..450cb050 100644 --- a/libs/openagent/tests/unit_tests/computer/test_local_vm_win.py +++ b/libs/openagent/tests/unit_tests/computer/test_local_vm_win.py @@ -293,7 +293,7 @@ class TestSession: async def test_creates_new_session(self) -> None: vm = _mock_vm() mgr = _make_manager(vm) - vm.shell = AsyncMock(side_effect=[_fail(), _ok(), _ok()]) + vm.shell = AsyncMock(side_effect=[_fail(), _ok(), _ok(), _ok()]) computer = await mgr.computer() @@ -327,7 +327,7 @@ async def test_rejects_resume_with_mounts(self) -> None: async def test_auto_starts_if_needed(self) -> None: vm = _mock_vm(status="Stopped") mgr = _make_manager(vm) - vm.shell = AsyncMock(side_effect=[_fail(), _ok(), _ok()]) + vm.shell = AsyncMock(side_effect=[_fail(), _ok(), _ok(), _ok()]) await mgr.computer() @@ -461,11 +461,11 @@ class TestCreateUser: async def test_creates_session_dirs(self) -> None: vm = _mock_vm() mgr = _make_manager(vm) - vm.shell = AsyncMock(side_effect=[_ok(), _ok()]) + vm.shell = AsyncMock(side_effect=[_ok(), _ok(), _ok()]) await mgr._create_user("test-user") - setup_call = vm.shell.call_args_list[1].args[0] + setup_call = vm.shell.call_args_list[2].args[0] home = "/sessions/test-user" for d in SESSION_DIRS: assert f"{home}/{d}" in setup_call @@ -479,6 +479,26 @@ async def test_useradd_failure_raises(self) -> None: with pytest.raises(VMError, match="Failed to create session user"): await mgr._create_user("test-user") + async def test_useradd_retries_without_k_flags(self) -> None: + vm = _mock_vm() + mgr = _make_manager(vm) + vm.shell = AsyncMock( + side_effect=[ + _ok(), # sudo probe + _fail(stderr="localized subordinate uid failure"), # useradd with -K... + _ok(), # fallback useradd without -K... + _ok(), # mkdir/chown + ] + ) + + await mgr._create_user("test-user") + + first_useradd = vm.shell.call_args_list[1].args[0] + fallback_useradd = vm.shell.call_args_list[2].args[0] + assert "-K SUB_UID_COUNT=0 -K SUB_GID_COUNT=0" in first_useradd + assert "-K SUB_UID_COUNT=0 -K SUB_GID_COUNT=0" not in fallback_useradd + assert "--no-log-init" in fallback_useradd + class TestNameGeneration: """Tests for _generate_unique_name.""" @@ -510,7 +530,7 @@ async def test_mount_failure_cleans_up_user(self, tmp_path: Any) -> None: mgr = _make_manager(vm) d = tmp_path / "nope" - vm.shell = AsyncMock(side_effect=[_fail(), _ok(), _ok(), _ok()]) + vm.shell = AsyncMock(side_effect=[_fail(), _ok(), _ok(), _ok(), _ok()]) with pytest.raises(ValueError, match="does not exist"): await mgr.computer(mounts=[Mount(source=str(d), target="proj")]) diff --git a/libs/openagent/tests/unit_tests/computer/test_wsl.py b/libs/openagent/tests/unit_tests/computer/test_wsl.py index 1da40b91..e039354d 100644 --- a/libs/openagent/tests/unit_tests/computer/test_wsl.py +++ b/libs/openagent/tests/unit_tests/computer/test_wsl.py @@ -372,6 +372,53 @@ def test_accepts_win32_with_wsl(self) -> None: assert vm.instance == "myvm" +# =========================================================================== +# WslVM start retry behavior +# =========================================================================== + + +class TestWslVMStart: + """Tests for WslVM.start().""" + + def _make_vm(self) -> WslVM: + with ( + patch("openagent.computer.local._wsl._PLATFORM", "win32"), + patch("shutil.which", return_value="C:\\Windows\\System32\\wsl.exe"), + ): + return WslVM(instance="test") + + async def test_start_retries_once_on_transient_minus_one_exit(self) -> None: + vm = self._make_vm() + + with ( + patch.object(vm, "status", new_callable=AsyncMock, return_value="Stopped"), + patch.object(vm, "_run_wsl", new_callable=AsyncMock) as mock_run_wsl, + patch.object(vm, "_apply_bind_mounts", new_callable=AsyncMock) as mock_apply, + ): + mock_run_wsl.side_effect = [ + WslError("wsl.exe failed (exit 4294967295): "), + "ok", + ] + + await vm.start() + + assert mock_run_wsl.await_count == 2 + mock_apply.assert_awaited_once() + + async def test_start_does_not_retry_on_non_transient_failure(self) -> None: + vm = self._make_vm() + + with ( + patch.object(vm, "status", new_callable=AsyncMock, return_value="Stopped"), + patch.object(vm, "_run_wsl", new_callable=AsyncMock, side_effect=WslError("wsl.exe failed (exit 1): boom")), + patch.object(vm, "_apply_bind_mounts", new_callable=AsyncMock) as mock_apply, + pytest.raises(WslError, match="exit 1"), + ): + await vm.start() + + mock_apply.assert_not_awaited() + + # =========================================================================== # Status output parsing # =========================================================================== From efeaeadcd4ec8ed0d801839ce4f798af71ca2eb4 Mon Sep 17 00:00:00 2001 From: xuelin-cell Date: Thu, 26 Mar 2026 19:02:56 +0800 Subject: [PATCH 09/28] fix(backend): preserve actionable cowork errors and improve WSL setup execution --- .../backend/openagent_api/agent_manager.py | 15 +++++--- .../backend/openagent_api/main.py | 11 ++++++ .../backend/openagent_api/routes/setup.py | 36 ++++++++++--------- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/libs/openagent_demo/backend/openagent_api/agent_manager.py b/libs/openagent_demo/backend/openagent_api/agent_manager.py index 87b35cc6..f0a24d96 100644 --- a/libs/openagent_demo/backend/openagent_api/agent_manager.py +++ b/libs/openagent_demo/backend/openagent_api/agent_manager.py @@ -196,14 +196,21 @@ async def _ensure_computer( "Please install and configure it in Settings \u2192 Sandbox." ) from None except Exception as exc: - # Convert VM infrastructure errors to user-friendly messages - msg = str(exc).lower() - if "not found" in msg or "does not exist" in msg or "not running" in msg: + # Preserve actionable details instead of collapsing all errors + # into "VM is not running", which can hide the real cause. + detail = str(exc).strip() or exc.__class__.__name__ + low = detail.lower() + if "does not exist" in low and "wsl distro" in low: + raise RuntimeError( + "Cowork mode requires VM setup. " + "Please install and configure it in Settings \u2192 Sandbox." + ) from None + if "not running" in low and "session" not in low: raise RuntimeError( "VM is not running. " "Please set it up in Settings \u2192 Sandbox." ) from None - raise + raise RuntimeError(f"Cowork session setup failed: {detail}") from None actual_name = computer.session_name self._computers[actual_name] = computer diff --git a/libs/openagent_demo/backend/openagent_api/main.py b/libs/openagent_demo/backend/openagent_api/main.py index fb9a366a..d41df59e 100644 --- a/libs/openagent_demo/backend/openagent_api/main.py +++ b/libs/openagent_demo/backend/openagent_api/main.py @@ -16,13 +16,24 @@ from fastapi.middleware.cors import CORSMiddleware from openagent_api.agent_manager import agent_manager +from openagent_api.paths import data_dir from openagent_api.routes import chat, config, conversations, sessions, setup, skills +_LOG_DIR = data_dir() / "logs" +_LOG_DIR.mkdir(parents=True, exist_ok=True) +_LOG_FILE = _LOG_DIR / "backend.log" + logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s", + handlers=[ + logging.StreamHandler(), + logging.FileHandler(_LOG_FILE, encoding="utf-8"), + ], + force=True, ) logger = logging.getLogger(__name__) +logger.info("Backend log file: %s", _LOG_FILE) async def _cleanup_expired_sessions() -> None: diff --git a/libs/openagent_demo/backend/openagent_api/routes/setup.py b/libs/openagent_demo/backend/openagent_api/routes/setup.py index 6d754e7a..deb0350e 100644 --- a/libs/openagent_demo/backend/openagent_api/routes/setup.py +++ b/libs/openagent_demo/backend/openagent_api/routes/setup.py @@ -483,18 +483,21 @@ def _win_path_to_wsl(path: Path | str) -> str: return f"/mnt/{drive}{rest}" -async def _wsl_shell(cmd: str, *, timeout: float = 60) -> tuple[int, str, str]: +async def _wsl_shell( + cmd: str, + *, + timeout: float = 60, + user: str | None = None, +) -> tuple[int, str, str]: wsl_exe = _wsl_cmd() if not wsl_exe: return 1, "", "wsl.exe not found" + exec_args: list[str] = [wsl_exe, "-d", _WSL_INSTANCE] + if user: + exec_args.extend(["-u", user]) + exec_args.extend(["--", "bash", "-lc", cmd]) proc = await asyncio.create_subprocess_exec( - wsl_exe, - "-d", - _WSL_INSTANCE, - "--", - "bash", - "-lc", - cmd, + *exec_args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) @@ -506,8 +509,8 @@ async def _wsl_shell(cmd: str, *, timeout: float = 60) -> tuple[int, str, str]: return 1, "", "Timed out" return ( proc.returncode or 0, - (stdout_b or b"").decode("utf-8", errors="replace"), - (stderr_b or b"").decode("utf-8", errors="replace"), + _decode_wsl_output(stdout_b or b""), + _decode_wsl_output(stderr_b or b""), ) @@ -1342,9 +1345,10 @@ async def _run_wsl(self, **kwargs: object) -> None: setup_wsl_quoted = shlex.quote(setup_wsl) setup_vm_dir_quoted = shlex.quote(_SETUP_VM_DIR) rc, _, err = await _wsl_shell( - f"sudo rm -rf {setup_vm_dir_quoted} && sudo mkdir -p {setup_vm_dir_quoted} && " - f"sudo cp -r {setup_wsl_quoted}/. {setup_vm_dir_quoted}/", + f"rm -rf {setup_vm_dir_quoted} && mkdir -p {setup_vm_dir_quoted} && " + f"cp -r {setup_wsl_quoted}/. {setup_vm_dir_quoted}/", timeout=60, + user="root", ) if rc != 0: self._emit("error", {"message": f"Failed to stage setup files in WSL: {err}"}) @@ -1353,7 +1357,7 @@ async def _run_wsl(self, **kwargs: object) -> None: return self._emit("progress", {"step": "starting", "message": "Starting provisioning..."}) - cmd = f"sudo bash {_SETUP_VM_DIR}/setup.sh" + cmd = f"bash {_SETUP_VM_DIR}/setup.sh" if force: cmd += " --force" @@ -1365,7 +1369,7 @@ async def _run_wsl(self, **kwargs: object) -> None: return proc = await asyncio.create_subprocess_exec( - wsl_exe, "-d", _WSL_INSTANCE, "--", "bash", "-lc", cmd, + wsl_exe, "-d", _WSL_INSTANCE, "-u", "root", "--", "bash", "-lc", cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) @@ -1414,7 +1418,7 @@ async def check_markers(self) -> dict[str, object]: """Read VM-side marker files to determine provision state.""" if sys.platform == "win32": instance_status = await _wsl_instance_status() - shell = _wsl_shell + shell = lambda cmd: _wsl_shell(cmd, user="root") else: instance_status = await _lima_instance_status() shell = _lima_shell @@ -1440,7 +1444,7 @@ async def check_markers(self) -> dict[str, object]: async def get_log(self) -> str: """Fetch the latest setup log from the VM.""" - shell = _wsl_shell if sys.platform == "win32" else _lima_shell + shell = (lambda cmd, timeout=15: _wsl_shell(cmd, timeout=timeout, user="root")) if sys.platform == "win32" else _lima_shell rc, stdout, _ = await shell( f"ls -t {_SETUP_LOG_DIR}/setup-*.log 2>/dev/null | head -1 | xargs cat 2>/dev/null | tail -500", timeout=15, From cf2b5450763a84eb7436f92095426ed33cbbc596 Mon Sep 17 00:00:00 2001 From: xuelin-cell Date: Thu, 26 Mar 2026 19:03:03 +0800 Subject: [PATCH 10/28] feat(electron): add WSL prerequisite precheck IPC and typed bridge --- libs/openagent_demo/electron/main.js | 151 +++++++++++++++++- libs/openagent_demo/electron/preload.js | 1 + .../openagent_demo/frontend/src/electron.d.ts | 14 ++ 3 files changed, 162 insertions(+), 4 deletions(-) diff --git a/libs/openagent_demo/electron/main.js b/libs/openagent_demo/electron/main.js index 2addbd09..3b06f145 100644 --- a/libs/openagent_demo/electron/main.js +++ b/libs/openagent_demo/electron/main.js @@ -211,24 +211,155 @@ function runCommand(cmd, args) { }); } +function tryParseJsonObject(text) { + const raw = (text || "").trim(); + if (!raw) return null; + const first = raw.indexOf("{"); + const last = raw.lastIndexOf("}"); + if (first < 0 || last <= first) return null; + try { + return JSON.parse(raw.slice(first, last + 1)); + } catch { + return null; + } +} + +async function checkWslPrerequisitesInternal() { + if (process.platform !== "win32") { + return { + ok: false, + code: "UNSUPPORTED_PLATFORM", + message: "This check is only available on Windows.", + }; + } + + const psScript = ` +$ErrorActionPreference = 'SilentlyContinue' +$cpu = Get-CimInstance Win32_Processor | Select-Object -First 1 VMMonitorModeExtensions,SecondLevelAddressTranslationExtensions,VirtualizationFirmwareEnabled +$cs = Get-CimInstance Win32_ComputerSystem | Select-Object -First 1 HypervisorPresent +$vmp = (Get-WindowsOptionalFeature -Online -FeatureName 'VirtualMachinePlatform').State +$wsl = (Get-WindowsOptionalFeature -Online -FeatureName 'Microsoft-Windows-Subsystem-Linux').State +$hypervisorAuto = $false +try { + $line = (bcdedit /enum '{current}' | Select-String -Pattern 'hypervisorlaunchtype' -SimpleMatch | Select-Object -First 1).ToString() + if ($line -match 'Auto') { $hypervisorAuto = $true } +} catch {} +$rebootPending = (Test-Path 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\RebootPending') -or (Test-Path 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update\\RebootRequired') +$vmMonitorRaw = $cpu.VMMonitorModeExtensions +$slatRaw = $cpu.SecondLevelAddressTranslationExtensions +$virtFirmwareRaw = $cpu.VirtualizationFirmwareEnabled +$vmMonitorKnown = $null -ne $vmMonitorRaw +$slatKnown = $null -ne $slatRaw +$virtFirmwareKnown = $null -ne $virtFirmwareRaw +$vmMonitor = [bool]$vmMonitorRaw +$slat = [bool]$slatRaw +$virtFirmware = [bool]$virtFirmwareRaw +$hypervisorPresent = [bool]$cs.HypervisorPresent +# Hypervisor already running => virtualization requirements are effectively met. +$virtualizationReady = $hypervisorPresent -or ($vmMonitor -and $slat -and $virtFirmware) +$vmpEnabled = ($vmp -eq 'Enabled') +$wslFeatureEnabled = ($wsl -eq 'Enabled') +$ok = $virtualizationReady -and $vmpEnabled -and $wslFeatureEnabled -and $hypervisorAuto +$code = 'OK' +$message = 'WSL prerequisites are ready.' +if ((-not $hypervisorPresent) -and (($vmMonitorKnown -and -not $vmMonitor) -or ($slatKnown -and -not $slat))) { + $ok = $false + $code = 'CPU_NOT_SUPPORTED' + $message = 'Your CPU does not meet WSL2 virtualization requirements (VM monitor mode + SLAT).' +} elseif (-not $vmpEnabled -or -not $wslFeatureEnabled) { + $ok = $false + $code = 'WINDOWS_FEATURES_DISABLED' + $message = 'Required Windows features are not enabled yet. Click Retry install to enable them automatically (admin permission), then restart Windows.' +} elseif ((-not $hypervisorPresent) -and $virtFirmwareKnown -and -not $virtFirmware) { + $ok = $false + $code = 'BIOS_VIRT_DISABLED' + $message = "Hardware virtualization is disabled in BIOS. Please enable Intel VT-x/AMD-V (SVM), save BIOS, then reboot Windows." +} elseif (-not $hypervisorAuto) { + $ok = $false + $code = 'HYPERVISOR_DISABLED' + $message = "Hypervisor launch is disabled. Click Retry install to fix it automatically, then restart Windows." +} +[pscustomobject]@{ + ok = $ok + code = $code + message = $message + virtualizationReady = $virtualizationReady + vmMonitorModeExtensions = $vmMonitor + slat = $slat + virtualizationFirmwareEnabled = $virtFirmware + hypervisorPresent = $hypervisorPresent + virtualMachinePlatformEnabled = $vmpEnabled + wslFeatureEnabled = $wslFeatureEnabled + hypervisorLaunchAuto = $hypervisorAuto + rebootPending = $rebootPending +} | ConvertTo-Json -Compress +`.trim(); + + const res = await runCommand("powershell.exe", [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + psScript, + ]); + + const parsed = tryParseJsonObject(`${res.stdout || ""}\n${res.stderr || ""}`); + if (parsed && typeof parsed === "object") { + return parsed; + } + return { + ok: false, + code: "CHECK_FAILED", + message: "Failed to check WSL prerequisites.", + }; +} + // ── IPC ────────────────────────────────────────────────────────────────────── ipcMain.on("get-backend-port", (event) => { event.returnValue = backendPort; }); +ipcMain.handle("check-wsl-prerequisites", async () => { + return checkWslPrerequisitesInternal(); +}); + ipcMain.handle("install-wsl-runtime", async () => { if (process.platform !== "win32") { return { ok: false, message: "This action is only available on Windows." }; } + const precheck = await checkWslPrerequisitesInternal(); + if (precheck?.code === "BIOS_VIRT_DISABLED" || precheck?.code === "CPU_NOT_SUPPORTED") { + return { + ok: false, + code: precheck.code, + message: precheck.message, + precheck, + }; + } + // Launch WSL installation with UAC elevation so non-technical users can // complete prerequisites in-app with one click. const psScript = ` $ErrorActionPreference = 'Stop' -$proc = Start-Process -FilePath "wsl.exe" -ArgumentList "--install","--no-distribution" -Verb RunAs -Wait -PassThru -if ($null -eq $proc) { exit 1 } -exit $proc.ExitCode +$wslPath = Join-Path $env:SystemRoot "System32\\wsl.exe" +if (-not (Test-Path $wslPath)) { + $wslPath = Join-Path $env:SystemRoot "Sysnative\\wsl.exe" +} +if (-not (Test-Path $wslPath)) { + throw "wsl.exe not found under %SystemRoot%." +} +try { + $proc = Start-Process -FilePath $wslPath -ArgumentList @("--install","--no-distribution") -Verb RunAs -Wait -PassThru + if ($null -eq $proc) { throw "Start-Process returned null process." } + exit $proc.ExitCode +} catch { + $msg = $_.Exception.Message + if ([string]::IsNullOrWhiteSpace($msg)) { $msg = "Unknown Start-Process failure." } + Write-Output ("INSTALL_ERR:" + $msg) + exit 1 +} `.trim(); const res = await runCommand("powershell.exe", [ @@ -255,15 +386,27 @@ exit $proc.ExitCode } const combined = `${res.stderr || ""}\n${res.stdout || ""}`.trim(); + const installErr = (combined.match(/INSTALL_ERR:(.*)/) || [null, ""])[1]?.trim(); const cancelled = /canceled|cancelled|拒绝|已取消|denied/i.test(combined); if (cancelled) { return { ok: false, exitCode: res.code, message: "Installation was cancelled." }; } + if (precheck?.code === "WINDOWS_FEATURES_DISABLED" || precheck?.code === "HYPERVISOR_DISABLED") { + return { + ok: false, + code: precheck.code, + exitCode: res.code, + message: + "WSL prerequisites are not fully enabled yet. Please allow the admin prompt, restart Windows, and retry VM setup.", + precheck, + }; + } + return { ok: false, exitCode: res.code, - message: combined || `Runtime installation failed (exit ${res.code}).`, + message: installErr || combined || `Runtime installation failed (exit ${res.code}).`, }; }); diff --git a/libs/openagent_demo/electron/preload.js b/libs/openagent_demo/electron/preload.js index cb15a785..279d4ef0 100644 --- a/libs/openagent_demo/electron/preload.js +++ b/libs/openagent_demo/electron/preload.js @@ -5,5 +5,6 @@ contextBridge.exposeInMainWorld("electronAPI", { backendPort: ipcRenderer.sendSync("get-backend-port"), isElectron: true, platform: process.platform, + checkWslPrerequisites: () => ipcRenderer.invoke("check-wsl-prerequisites"), installWslRuntime: () => ipcRenderer.invoke("install-wsl-runtime"), }); diff --git a/libs/openagent_demo/frontend/src/electron.d.ts b/libs/openagent_demo/frontend/src/electron.d.ts index 61b88d74..ed453916 100644 --- a/libs/openagent_demo/frontend/src/electron.d.ts +++ b/libs/openagent_demo/frontend/src/electron.d.ts @@ -6,8 +6,22 @@ declare global { backendPort?: number; isElectron?: boolean; platform?: string; + checkWslPrerequisites?: () => Promise<{ + ok: boolean; + code?: string; + message?: string; + virtualizationReady?: boolean; + vmMonitorModeExtensions?: boolean; + slat?: boolean; + virtualizationFirmwareEnabled?: boolean; + virtualMachinePlatformEnabled?: boolean; + wslFeatureEnabled?: boolean; + hypervisorLaunchAuto?: boolean; + rebootPending?: boolean; + }>; installWslRuntime?: () => Promise<{ ok: boolean; + code?: string; rebootRequired?: boolean; exitCode?: number; message?: string; From 163fda75fb9bda061e83d5b72a6d9bc6410a4f72 Mon Sep 17 00:00:00 2001 From: xuelin-cell Date: Thu, 26 Mar 2026 19:03:12 +0800 Subject: [PATCH 11/28] feat(frontend): improve VM setup flow and add global reboot-required modal --- libs/openagent_demo/frontend/src/App.css | 108 ++++++++ libs/openagent_demo/frontend/src/App.tsx | 6 + libs/openagent_demo/frontend/src/api.ts | 1 + .../frontend/src/components/ChatArea.tsx | 11 +- .../src/components/OnboardingWizard.tsx | 11 +- .../src/components/RestartRequiredModal.tsx | 48 ++++ .../frontend/src/components/SettingsModal.tsx | 26 +- libs/openagent_demo/frontend/src/store.ts | 27 ++ libs/openagent_demo/frontend/src/vmSetup.tsx | 238 ++++++++++++------ 9 files changed, 401 insertions(+), 75 deletions(-) create mode 100644 libs/openagent_demo/frontend/src/components/RestartRequiredModal.tsx diff --git a/libs/openagent_demo/frontend/src/App.css b/libs/openagent_demo/frontend/src/App.css index da546366..1c30d7d8 100644 --- a/libs/openagent_demo/frontend/src/App.css +++ b/libs/openagent_demo/frontend/src/App.css @@ -5792,6 +5792,114 @@ } } +/* ===== Global Restart Required Modal ===== */ +.restart-required-overlay { + position: fixed; + inset: 0; + z-index: 12000; + background: rgba(0, 0, 0, 0.58); + backdrop-filter: blur(3px); + -webkit-backdrop-filter: blur(3px); + display: flex; + align-items: center; + justify-content: center; + padding: 20px; +} + +.restart-required-modal { + width: min(560px, 94vw); + background: var(--bg-primary); + border: 1px solid rgba(248, 113, 113, 0.35); + border-radius: 14px; + padding: 20px 22px; + box-shadow: 0 24px 60px rgba(0, 0, 0, 0.45); +} + +.restart-required-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 10px; +} + +.restart-required-icon { + color: var(--danger, #ef4444); + flex-shrink: 0; +} + +.restart-required-title { + margin: 0; + font-size: 20px; + line-height: 1.25; + color: var(--text-primary); +} + +.restart-required-text { + margin: 0 0 8px; + font-size: 14px; + line-height: 1.55; + color: var(--text-primary); +} + +.restart-required-text--strong { + color: var(--danger, #ef4444); + font-weight: 600; +} + +.restart-required-detail { + margin: 4px 0 0; + padding: 10px 12px; + border-radius: 8px; + border: 1px solid var(--border); + background: var(--bg-secondary); + color: var(--text-secondary); + font-size: 12px; + line-height: 1.45; + word-break: break-word; +} + +.restart-required-actions { + margin-top: 16px; + display: flex; + gap: 8px; + justify-content: flex-end; + flex-wrap: wrap; +} + +.restart-required-btn { + display: inline-flex; + align-items: center; + gap: 6px; + border-radius: 10px; + border: 1px solid var(--border); + padding: 8px 12px; + font-size: 13px; + font-weight: 600; + cursor: pointer; + transition: background 120ms ease, border-color 120ms ease, color 120ms ease; +} + +.restart-required-btn--ghost { + background: transparent; + color: var(--text-primary); +} + +.restart-required-btn--ghost:hover { + background: var(--bg-hover); + border-color: var(--border-hover); +} + +.restart-required-btn--primary { + background: var(--accent); + border-color: var(--accent); + color: #fff; +} + +.restart-required-btn--primary:hover { + background: var(--accent-hover); + border-color: var(--accent-hover); +} + /* ===== VM Setup Floating Indicator ===== */ .vm-floater { diff --git a/libs/openagent_demo/frontend/src/App.tsx b/libs/openagent_demo/frontend/src/App.tsx index 26d9dcf6..6028c720 100644 --- a/libs/openagent_demo/frontend/src/App.tsx +++ b/libs/openagent_demo/frontend/src/App.tsx @@ -10,6 +10,7 @@ import type { Tab as SettingsTab } from "./components/SettingsModal"; import OnboardingWizard from "./components/OnboardingWizard"; import SearchModal from "./components/SearchModal"; import Toast from "./components/Toast"; +import RestartRequiredModal from "./components/RestartRequiredModal"; import VMSetupFloater from "./components/VMSetupFloater"; import { VMSetupProvider } from "./vmSetup"; import type { Attachment, ConversationMode, Message } from "./types"; @@ -425,6 +426,11 @@ function App() { notifications={state.notifications} onDismiss={(id) => dispatch({ type: "DISMISS_NOTIFICATION", payload: id })} /> + openSettings("sandbox")} + /> ); diff --git a/libs/openagent_demo/frontend/src/api.ts b/libs/openagent_demo/frontend/src/api.ts index 204ad387..d58bb32f 100644 --- a/libs/openagent_demo/frontend/src/api.ts +++ b/libs/openagent_demo/frontend/src/api.ts @@ -419,6 +419,7 @@ export interface VMStatus { managed?: boolean; reason?: string; instance_status?: string | null; + instance_error?: string | null; vm_ready?: boolean; } diff --git a/libs/openagent_demo/frontend/src/components/ChatArea.tsx b/libs/openagent_demo/frontend/src/components/ChatArea.tsx index c655af24..8bf6a35d 100644 --- a/libs/openagent_demo/frontend/src/components/ChatArea.tsx +++ b/libs/openagent_demo/frontend/src/components/ChatArea.tsx @@ -1,6 +1,7 @@ import { useState, useCallback, useRef, useEffect, useMemo } from "react"; import { PanelRight } from "lucide-react"; import { useAppContext } from "../store"; +import { getVMStatus } from "../api"; import WelcomeScreen from "./WelcomeScreen"; import MessageList from "./MessageList"; import ChatInput from "./ChatInput"; @@ -72,7 +73,15 @@ export default function ChatArea({ conversation, onSendMessage, onOpenSettings, const isMac = navigator.platform.toUpperCase().includes("MAC"); const handleModeChange = useCallback( - (mode: ConversationMode) => { + async (mode: ConversationMode) => { + if (mode === "cowork") { + try { + const vs = await getVMStatus(); + dispatch({ type: "SET_VM_STATUS", payload: vs }); + } catch { + // Best-effort refresh; keep UX responsive. + } + } dispatch({ type: "SET_SELECTED_MODE", payload: mode }); }, [dispatch] diff --git a/libs/openagent_demo/frontend/src/components/OnboardingWizard.tsx b/libs/openagent_demo/frontend/src/components/OnboardingWizard.tsx index c5ba2249..8b9be968 100644 --- a/libs/openagent_demo/frontend/src/components/OnboardingWizard.tsx +++ b/libs/openagent_demo/frontend/src/components/OnboardingWizard.tsx @@ -139,6 +139,7 @@ export default function OnboardingWizard({ open, onComplete, settings, onSetting const vmPhase2Error = vm.phase2Error; const vmPhase3 = vm.phase3; const vmUsable = vmPhase1 === "done" && vmPhase2 === "done"; + const vmPhase1NeedsRestart = /restart windows|重启.*windows|重启.*电脑|reboot/i.test(vmPhase1Error || ""); // Load server config and reset name on open useEffect(() => { @@ -822,7 +823,15 @@ export default function OnboardingWizard({ open, onComplete, settings, onSetting )} {vmPhase1 === "error" && ( - + vmPhase1NeedsRestart ? ( + + ) : ( + + ) )}
{vmPhase1 === "error" && vmPhase1Error && ( diff --git a/libs/openagent_demo/frontend/src/components/RestartRequiredModal.tsx b/libs/openagent_demo/frontend/src/components/RestartRequiredModal.tsx new file mode 100644 index 00000000..15354fdd --- /dev/null +++ b/libs/openagent_demo/frontend/src/components/RestartRequiredModal.tsx @@ -0,0 +1,48 @@ +import { AlertTriangle, RefreshCw, Settings } from "lucide-react"; +import { useVMSetup } from "../vmSetup"; + +interface RestartRequiredModalProps { + open: boolean; + message: string; + onOpenSettings: () => void; +} + +export default function RestartRequiredModal({ + open, + message, + onOpenSettings, +}: RestartRequiredModalProps) { + const vm = useVMSetup(); + + if (!open) return null; + + return ( +
+
+
+ +

+ Restart Required +

+
+

+ WSL runtime installation is complete, but Windows must restart before OpenAgent can continue. +

+

+ Please restart your computer now, otherwise VM/Cowork features will not work. +

+ {message ?

{message}

: null} +
+ + +
+
+
+ ); +} diff --git a/libs/openagent_demo/frontend/src/components/SettingsModal.tsx b/libs/openagent_demo/frontend/src/components/SettingsModal.tsx index ec6e9d63..c2009b23 100644 --- a/libs/openagent_demo/frontend/src/components/SettingsModal.tsx +++ b/libs/openagent_demo/frontend/src/components/SettingsModal.tsx @@ -1733,9 +1733,9 @@ function SandboxTab({ config, onConfigChange }: ConfigTabProps) { } }; - // Cowork mode is usable once Lima is installed and VM is running (phases 1+2). - // Phase 3 (dependency installation) can run in the background. - const vmUsable = phase1 === "done" && phase2 === "done"; + // Cowork mode should follow backend truth from /api/setup/vm (vm_ready). + // Keep phase fallback only when status payload is unavailable. + const vmUsable = vmStatus?.vm_ready ?? (phase1 === "done" && phase2 === "done"); const allDone = vmUsable && phase3 === "done"; const anyRunning = phase1 === "running" || phase2 === "running" || phase3 === "running"; const coreError = phase1 === "error" || phase2 === "error"; @@ -1857,6 +1857,26 @@ function SandboxTab({ config, onConfigChange }: ConfigTabProps) { {phase1 === "error" && phase1Error && (

{phase1Error}

)} + {phase1 === "error" && /bios|firmware|vt-x|amd-v|svm|virtualization is disabled|BIOS_VIRT_DISABLED|virtualization/i.test(phase1Error || "") && ( +
+

+ We cannot enable BIOS virtualization remotely, but you can finish it in 3 steps: +

+
    +
  1. Restart and enter BIOS/UEFI setup.
  2. +
  3. Enable virtualization.
  4. +
  5. Save and reboot Windows, then click Retry.
  6. +
+

Common option names:

+
    +
  • Intel: Intel Virtualization Technology / VT-x
  • +
  • AMD: SVM Mode / AMD-V
  • +
+

+ Common BIOS keys: F2, Del, Esc, F10, F12 (varies by brand). +

+
+ )} {phase1 === "done" && (

{vmBackendName} is installed and available.

)} diff --git a/libs/openagent_demo/frontend/src/store.ts b/libs/openagent_demo/frontend/src/store.ts index 80bbe71b..456e98e3 100644 --- a/libs/openagent_demo/frontend/src/store.ts +++ b/libs/openagent_demo/frontend/src/store.ts @@ -15,6 +15,11 @@ export interface Notification { type: "error" | "info" | "success"; } +export interface RestartRequiredModalState { + open: boolean; + message: string; +} + export interface AppState { conversations: Conversation[]; activeConversationId: string | null; @@ -39,6 +44,7 @@ export interface AppState { /** Remembers the last active conversation (or null=welcome) per mode. */ lastActiveByMode: Record; notifications: Notification[]; + restartRequiredModal: RestartRequiredModalState; filePreview: { path: string; mimeType: string; conversationId: string } | null; filePreviewVisible: boolean; /** Saved right-panel state before file preview opened (for restore on close). */ @@ -66,6 +72,7 @@ export const initialState: AppState = { })(), lastActiveByMode: { chat: null, cowork: null }, notifications: [], + restartRequiredModal: { open: false, message: "" }, filePreview: null, filePreviewVisible: false, rightPanelBeforePreview: null, @@ -104,6 +111,8 @@ export type Action = | { type: "SET_SELECTED_MODE"; payload: ConversationMode } | { type: "SHOW_NOTIFICATION"; payload: { message: string; type: "error" | "info" | "success" } } | { type: "DISMISS_NOTIFICATION"; payload: string } + | { type: "SHOW_RESTART_REQUIRED_MODAL"; payload: { message: string } } + | { type: "HIDE_RESTART_REQUIRED_MODAL" } | { type: "SET_FILE_PREVIEW"; payload: { path: string; mimeType: string; conversationId: string } | null } | { type: "SET_FILE_PREVIEW_VISIBLE"; payload: boolean }; @@ -772,6 +781,24 @@ export function reducer(state: AppState, action: Action): AppState { notifications: state.notifications.filter((n) => n.id !== action.payload), }; + case "SHOW_RESTART_REQUIRED_MODAL": + return { + ...state, + restartRequiredModal: { + open: true, + message: action.payload.message, + }, + }; + + case "HIDE_RESTART_REQUIRED_MODAL": + return { + ...state, + restartRequiredModal: { + open: false, + message: "", + }, + }; + case "SET_FILE_PREVIEW": { const rpConvId = state.activeConversationId; const currentPanelVisible = rpConvId ? (state.rightPanelByConversation[rpConvId] ?? false) : false; diff --git a/libs/openagent_demo/frontend/src/vmSetup.tsx b/libs/openagent_demo/frontend/src/vmSetup.tsx index b4b3c7f9..742bac12 100644 --- a/libs/openagent_demo/frontend/src/vmSetup.tsx +++ b/libs/openagent_demo/frontend/src/vmSetup.tsx @@ -27,6 +27,23 @@ import { import type { VMStatus, ProvisionStepDef } from "./api"; import { useAppContext } from "./store"; +const WSL_MANUAL_INSTALL_CMD = "wsl --install --no-distribution"; +const IS_WINDOWS = navigator.platform.toUpperCase().includes("WIN"); +const RESTART_REQUIRED_PATTERN = /restart windows|restart your computer|reboot|重启|重新启动/i; + +function buildWslInstallRecoveryMessage(detail?: string): string { + const base = + `Automatic runtime installation failed. Run this in an Administrator PowerShell: ${WSL_MANUAL_INSTALL_CMD}. ` + + "Then restart Windows and click Retry. You can skip Local VM for now and use E2B Sandbox (chat mode)."; + const trimmed = (detail || "").trim(); + if (!trimmed) return base; + return `${base} Details: ${trimmed}`; +} + +function isRestartRequiredMessage(detail?: string | null): boolean { + return RESTART_REQUIRED_PATTERN.test((detail || "").trim()); +} + // ── Types ── export type PhaseStatus = "checking" | "pending" | "running" | "done" | "error"; @@ -90,6 +107,7 @@ export function VMSetupProvider({ children }: { children: ReactNode }) { const [provStepStatus, setProvStepStatus] = useState>({}); const [provStepMsg, setProvStepMsg] = useState>({}); const [provLog, setProvLog] = useState(null); + const autoBootstrapTriggeredRef = useRef(false); // SSE abort controllers (kept alive across renders, never aborted on unmount) const installCtrl = useRef(null); @@ -108,7 +126,16 @@ export function VMSetupProvider({ children }: { children: ReactNode }) { dispatch({ type: "SHOW_NOTIFICATION", payload: { message, type } }); }; + const showRestartRequiredModal = (message: string) => { + dispatch({ type: "SHOW_RESTART_REQUIRED_MODAL", payload: { message } }); + }; + + const hideRestartRequiredModal = () => { + dispatch({ type: "HIDE_RESTART_REQUIRED_MODAL" }); + }; + const doRecheckVmEngine = async () => { + hideRestartRequiredModal(); setPhase1("checking"); setPhase1Msg("Re-checking runtime status..."); setPhase1Error(""); @@ -123,6 +150,7 @@ export function VMSetupProvider({ children }: { children: ReactNode }) { return; } if (vs.installed) { + hideRestartRequiredModal(); setPhase1("done"); setPhase1Msg(""); setPhase1Error(""); @@ -132,6 +160,7 @@ export function VMSetupProvider({ children }: { children: ReactNode }) { setPhase1("error"); setPhase1Msg(""); setPhase1Error(vs.reason); + if (isRestartRequiredMessage(vs.reason)) showRestartRequiredModal(vs.reason); } else { setPhase1("pending"); setPhase1Msg(""); @@ -221,7 +250,8 @@ export function VMSetupProvider({ children }: { children: ReactNode }) { // ── Phase 1: Install Lima ── - const doInstallLima = () => { + const doInstallLima = async () => { + hideRestartRequiredModal(); const inferredBackend = navigator.platform.toUpperCase().includes("WIN") ? "wsl" : "lima"; const currentBackend = vmStatus?.backend ?? inferredBackend; const canAssistWslInstall = @@ -230,52 +260,84 @@ export function VMSetupProvider({ children }: { children: ReactNode }) { typeof window.electronAPI?.installWslRuntime === "function"; if (canAssistWslInstall) { - setPhase1("running"); + setPhase1("checking"); setPhase1Error(""); - setPhase1Msg("Installing required Windows runtime (admin permission needed)..."); + setPhase1Msg("Checking Windows virtualization prerequisites..."); - window.electronAPI! - .installWslRuntime!() - .then(async (res) => { - if (!res.ok) { - const msg = res.message || "Runtime installation failed."; + try { + if (typeof window.electronAPI?.checkWslPrerequisites === "function") { + const pre = await window.electronAPI.checkWslPrerequisites(); + const autoFixable = + pre?.code === "WINDOWS_FEATURES_DISABLED" || + pre?.code === "HYPERVISOR_DISABLED"; + if (!pre.ok && !autoFixable) { setPhase1("error"); - setPhase1Error(msg); - notify(msg, "error"); + setPhase1Msg(""); + setPhase1Error(pre.message || "WSL prerequisites are not ready."); + notify(pre.message || "WSL prerequisites are not ready.", "error"); return; } - - if (res.rebootRequired) { - const msg = res.message || "Runtime installed. Please restart Windows before continuing VM setup."; + if (!pre.ok && autoFixable) { + notify("Windows features are not enabled yet. OpenAgent will try to enable them with admin permission.", "info"); + } + if (pre.rebootPending) { + const msg = "Windows restart is pending. Please restart first, then continue VM setup."; setPhase1("error"); setPhase1Msg(""); setPhase1Error(msg); notify(msg, "info"); + showRestartRequiredModal(msg); return; } + } - const vs = await getVMStatus(); - setVmStatus(vs); - dispatch({ type: "SET_VM_STATUS", payload: vs }); + setPhase1("running"); + setPhase1Error(""); + setPhase1Msg("Installing required Windows runtime (admin permission needed)..."); - if (vs.installed) { - setPhase1("done"); - setPhase1Msg(""); - notify("Runtime installed", "success"); - setPhase2("pending"); - attachBuild(); - } else { - setPhase1("pending"); - setPhase1Msg("Runtime install command completed. Click Retry if needed."); - notify("Runtime install command completed", "info"); - } - }) - .catch((err: unknown) => { - const msg = err instanceof Error ? err.message : String(err); + const res = await window.electronAPI!.installWslRuntime!(); + if (!res.ok) { + const raw = res.message || "Runtime installation failed."; + const msg = buildWslInstallRecoveryMessage(raw); + setPhase1("error"); + setPhase1Error(msg); + notify("Automatic runtime installation failed. See VM Engine details for manual steps.", "error"); + return; + } + + if (res.rebootRequired) { + const msg = res.message || "Runtime installed. Please restart Windows before continuing VM setup."; setPhase1("error"); + setPhase1Msg(""); setPhase1Error(msg); - notify(`Runtime installation failed: ${msg}`, "error"); - }); + notify(msg, "info"); + showRestartRequiredModal(msg); + return; + } + + const vs = await getVMStatus(); + setVmStatus(vs); + dispatch({ type: "SET_VM_STATUS", payload: vs }); + + if (vs.installed) { + hideRestartRequiredModal(); + setPhase1("done"); + setPhase1Msg(""); + notify("Runtime installed", "success"); + setPhase2("pending"); + attachBuild(); + } else { + setPhase1("pending"); + setPhase1Msg("Runtime install command completed. Click Retry if needed."); + notify("Runtime install command completed", "info"); + } + } catch (err: unknown) { + const raw = err instanceof Error ? err.message : String(err); + const msg = buildWslInstallRecoveryMessage(raw); + setPhase1("error"); + setPhase1Error(msg); + notify("Automatic runtime installation failed. See VM Engine details for manual steps.", "error"); + } return; } @@ -352,20 +414,25 @@ export function VMSetupProvider({ children }: { children: ReactNode }) { vmStatusSnapshot = vs; if (cancelled) return; setVmStatus(vs); - if (!vs.supported) { - setPhase1("error"); - setPhase1Error("Not supported on this platform"); - return; - } - if (vs.installed) { - setPhase1("done"); - limaInstalled = true; - } else if (vs.reason) { - setPhase1("error"); - setPhase1Error(vs.reason); - } else { - setPhase1("pending"); - } + dispatch({ type: "SET_VM_STATUS", payload: vs }); + if (!vs.supported) { + hideRestartRequiredModal(); + setPhase1("error"); + setPhase1Error("Not supported on this platform"); + return; + } + if (vs.installed) { + hideRestartRequiredModal(); + setPhase1("done"); + limaInstalled = true; + } else if (vs.reason) { + setPhase1("error"); + setPhase1Error(vs.reason); + if (isRestartRequiredMessage(vs.reason)) showRestartRequiredModal(vs.reason); + } else { + hideRestartRequiredModal(); + setPhase1("pending"); + } } catch { if (cancelled) return; setPhase1("error"); @@ -378,32 +445,39 @@ export function VMSetupProvider({ children }: { children: ReactNode }) { // Phase 2: VM instance (Lima must be Running; WSL is on-demand — Stopped is OK) const runningLabels = ["Running", "正在运行"]; const wslStoppedLabels = ["Stopped", "已停止"]; - let vmReady = false; - try { - const bs = await getVMBuildStatus(); - if (cancelled) return; - if (bs.status === "running") { - // Re-attach to an in-progress build - attachBuild(); - } else if (bs.vm_state && runningLabels.includes(bs.vm_state)) { - setPhase2("done"); - vmReady = true; - } else if ( - vmStatusSnapshot?.backend === "wsl" && - bs.vm_state && - wslStoppedLabels.includes(bs.vm_state) - ) { - setPhase2("done"); - vmReady = true; - } else if (bs.vm_state === "Stopped") { - setPhase2("pending"); - setPhase2Msg("VM exists but is stopped"); - } else { + let vmReady = vmStatusSnapshot?.vm_ready === true; + if (vmReady) { + setPhase2("done"); + } else if (vmStatusSnapshot?.instance_error) { + setPhase2("error"); + setPhase2Error(vmStatusSnapshot.instance_error); + } else { + try { + const bs = await getVMBuildStatus(); + if (cancelled) return; + if (bs.status === "running") { + // Re-attach to an in-progress build + attachBuild(); + } else if (bs.vm_state && runningLabels.includes(bs.vm_state)) { + setPhase2("done"); + vmReady = true; + } else if ( + vmStatusSnapshot?.backend === "wsl" && + bs.vm_state && + wslStoppedLabels.includes(bs.vm_state) + ) { + setPhase2("done"); + vmReady = true; + } else if (bs.vm_state === "Stopped") { + setPhase2("pending"); + setPhase2Msg("VM exists but is stopped"); + } else { + setPhase2("pending"); + } + } catch { + if (cancelled) return; setPhase2("pending"); } - } catch { - if (cancelled) return; - setPhase2("pending"); } if (!vmReady) return; @@ -441,6 +515,30 @@ export function VMSetupProvider({ children }: { children: ReactNode }) { // ── Context value ── + // Windows-only auto setup: + // automatically trigger VM Engine + VM Instance on startup so users + // don't need to click "Install" manually after app installation. + useEffect(() => { + if (!IS_WINDOWS) return; + if (autoBootstrapTriggeredRef.current) return; + if (!vmStatus?.supported) return; + if (phase1 === "checking") return; + if (phase1 === "running" || phase2 === "running") return; + + if (phase1 === "pending") { + autoBootstrapTriggeredRef.current = true; + notify("Detected first-time Windows setup. Starting VM runtime install automatically...", "info"); + void doInstallLima(); + return; + } + + if (phase1 === "done" && phase2 === "pending") { + autoBootstrapTriggeredRef.current = true; + notify("Runtime is ready. Starting VM instance setup automatically...", "info"); + attachBuild(); + } + }, [vmStatus, phase1, phase2]); // eslint-disable-line react-hooks/exhaustive-deps + const value: VMSetupContextValue = { vmStatus, phase1, phase1Msg, phase1Error, From 3b4c892319b898e489a5d0cd296e296005d935f1 Mon Sep 17 00:00:00 2001 From: xuelin-cell Date: Thu, 26 Mar 2026 19:03:45 +0800 Subject: [PATCH 12/28] docs: add windows regression checklist and vm prebuilt inventory --- .../windows-installer-regression-checklist.md | 73 ++ .../apt-installed-new.txt | Bin 0 -> 3090 bytes .../apt-installed.txt | 964 ++++++++++++++++++ .../new_dpkg_status.txt | Bin 0 -> 769308 bytes .../npm-global-new.txt | Bin 0 -> 90 bytes .../npm-global.txt | 22 + .../pip-dist-info.txt | 83 ++ .../pip-freeze-new.txt | Bin 0 -> 1246 bytes .../rebuild-comparison.md | 52 + 9 files changed, 1194 insertions(+) create mode 100644 docs/windows-installer-regression-checklist.md create mode 100644 reports/vm-prebuilt-inventory-20260325/apt-installed-new.txt create mode 100644 reports/vm-prebuilt-inventory-20260325/apt-installed.txt create mode 100644 reports/vm-prebuilt-inventory-20260325/new_dpkg_status.txt create mode 100644 reports/vm-prebuilt-inventory-20260325/npm-global-new.txt create mode 100644 reports/vm-prebuilt-inventory-20260325/npm-global.txt create mode 100644 reports/vm-prebuilt-inventory-20260325/pip-dist-info.txt create mode 100644 reports/vm-prebuilt-inventory-20260325/pip-freeze-new.txt create mode 100644 reports/vm-prebuilt-inventory-20260325/rebuild-comparison.md diff --git a/docs/windows-installer-regression-checklist.md b/docs/windows-installer-regression-checklist.md new file mode 100644 index 00000000..c72c92a2 --- /dev/null +++ b/docs/windows-installer-regression-checklist.md @@ -0,0 +1,73 @@ +# Windows Installer Regression Checklist (No-WSL Machine) + +## 0. Test Environment +- Fresh Windows 10/11 x64 machine (no OpenAgent, no WSL distro). +- Verify: + - `wsl --status` shows not installed or unavailable. + - `wsl -l -v` has no `openagent` distro. + +## 1. Install App +- Run `OpenAgent-0.0.1-win-x64.exe`. +- Open OpenAgent -> Settings -> Sandbox. +- Expected: + - App launches normally. + - No crash on Sandbox page. + +## 2. Assisted WSL Install (First-time) +- Click VM setup / Retry on `VM Engine`. +- Expected: + - If WSL missing: clear guided message appears. + - App attempts assisted install (with clear status text). + - If system needs reboot, UI explicitly asks reboot. + +## 3. Reboot Required Path +- If prompted, reboot Windows. +- Re-open OpenAgent -> Sandbox. +- Click Retry again. +- Expected: + - `VM Instance` eventually becomes `Ready`. + - No `exit 4294967295` generic toast without guidance. + +## 4. VM Dependencies Install +- Click install for `VM System Dependencies`. +- Expected: + - Progress steps update continuously. + - Failure (if any) includes actionable reason. + - Success ends with green/ready state. + +## 5. Cowork Functional Smoke Test +- Set cowork folder to a writable path, e.g. `D:\code\agentTest`. +- In chat: + - Create folder `sport`. + - Then create `sport\basketball`. +- Expected: + - Both operations succeed. + - No false "current directory not writable" error. + +## 6. Non-Technical User UX Check +- Trigger BIOS/virtualization-disabled scenario (or validate copy via mocked error). +- Expected: + - Error includes clear next steps: + - Enable virtualization in BIOS (Intel VT-x / AMD SVM). + - Save BIOS and reboot. + - Retry in app. + - Language is understandable for non-technical users. + +## 7. Log Collection (On Failure) +- Collect: + - OpenAgent backend logs. + - Sandbox setup UI log panel output. + - `wsl --status` + - `wsl -l -v` +- Record exact toast error text and timestamp. + +## 8. Release Gate (Pass Criteria) +- Must pass: + - App install + launch. + - WSL guided install path. + - Reboot path. + - VM Instance ready. + - VM dependencies ready. + - Cowork nested directory creation. +- Block release if any of above fails. + diff --git a/reports/vm-prebuilt-inventory-20260325/apt-installed-new.txt b/reports/vm-prebuilt-inventory-20260325/apt-installed-new.txt new file mode 100644 index 0000000000000000000000000000000000000000..42f8bd6ff8c23dc3f52b2957a960fff46f4aa2c1 GIT binary patch literal 3090 ycmezW&z8ZKftP^`NRHB@;V_yGM)SdFIWSrdjFtnV<-lk;Fj@|bmIEWL8~^|WaonH) literal 0 HcmV?d00001 diff --git a/reports/vm-prebuilt-inventory-20260325/apt-installed.txt b/reports/vm-prebuilt-inventory-20260325/apt-installed.txt new file mode 100644 index 00000000..df481158 --- /dev/null +++ b/reports/vm-prebuilt-inventory-20260325/apt-installed.txt @@ -0,0 +1,964 @@ +adduser==3.137ubuntu1 +adwaita-icon-theme==46.0-1 +apparmor==4.0.1really4.0.1-0ubuntu0.24.04.5 +apport==2.28.1-0ubuntu3.8 +apport-core-dump-handler==2.28.1-0ubuntu3.8 +apport-symptoms==0.25 +appstream==1.0.2-1build6 +apt==2.8.3 +apt-transport-https==2.8.3 +apt-utils==2.8.3 +at-spi2-common==2.52.0-1build1 +at-spi2-core==2.52.0-1build1 +base-files==13ubuntu10.4 +base-passwd==3.6.3build1 +bash==5.2.21-2ubuntu4 +bash-completion==1:2.11-8 +bc==1.07.1-3ubuntu4 +binutils==2.42-4ubuntu2.8 +binutils-common==2.42-4ubuntu2.8 +binutils-x86-64-linux-gnu==2.42-4ubuntu2.8 +bsdextrautils==2.39.3-9ubuntu6.5 +bsdutils==1:2.39.3-9ubuntu6.5 +build-essential==12.10ubuntu1 +byobu==6.11-0ubuntu1 +bzip2==1.0.8-5.1build0.1 +ca-certificates==20240203 +ca-certificates-java==20240118 +cloud-guest-utils==0.33-1 +cloud-init==25.2-0ubuntu1~24.04.1 +command-not-found==23.04.0 +console-setup==1.226ubuntu1 +console-setup-linux==1.226ubuntu1 +coreutils==9.4-3ubuntu6.2 +cpp==4:13.2.0-7ubuntu1 +cpp-13==13.3.0-6ubuntu2~24.04.1 +cpp-13-x86-64-linux-gnu==13.3.0-6ubuntu2~24.04.1 +cpp-x86-64-linux-gnu==4:13.2.0-7ubuntu1 +cron==3.0pl1-184ubuntu2 +cron-daemon-common==3.0pl1-184ubuntu2 +curl==8.5.0-2ubuntu10.8 +dash==0.5.12-6ubuntu5 +dbus==1.14.10-4ubuntu4.1 +dbus-bin==1.14.10-4ubuntu4.1 +dbus-daemon==1.14.10-4ubuntu4.1 +dbus-session-bus-common==1.14.10-4ubuntu4.1 +dbus-system-bus-common==1.14.10-4ubuntu4.1 +dbus-user-session==1.14.10-4ubuntu4.1 +dbus-x11==1.14.10-4ubuntu4.1 +dconf-gsettings-backend==0.40.0-4ubuntu0.1 +dconf-service==0.40.0-4ubuntu0.1 +debconf==1.5.86ubuntu1 +debconf-i18n==1.5.86ubuntu1 +debianutils==5.17build1 +default-jre-headless==2:1.21-75+exp1 +dhcpcd-base==1:10.0.6-1ubuntu3.2 +diffutils==1:3.10-1build1 +dirmngr==2.4.4-2ubuntu17.4 +distro-info==1.7build1 +distro-info-data==0.60ubuntu0.5 +dmsetup==2:1.02.185-3ubuntu3.2 +dpkg==1.22.6ubuntu6.5 +dpkg-dev==1.22.6ubuntu6.5 +e2fsprogs==1.47.0-2.4~exp1ubuntu4.1 +e2fsprogs-l10n==1.47.0-2.4~exp1ubuntu4.1 +eatmydata==131-1ubuntu1 +ed==1.20.1-1 +eject==2.39.3-9ubuntu6.5 +ethtool==1:6.7-1build1 +fdisk==2.39.3-9ubuntu6.5 +ffmpeg==7:6.1.1-3ubuntu5 +file==1:5.45-3build1 +findutils==4.9.0-5build1 +fontconfig==2.15.0-1.1ubuntu2 +fontconfig-config==2.15.0-1.1ubuntu2 +fonts-crosextra-caladea==20200211-2 +fonts-crosextra-carlito==20230309-2 +fonts-dejavu==2.37-8 +fonts-dejavu-core==2.37-8 +fonts-dejavu-extra==2.37-8 +fonts-dejavu-mono==2.37-8 +fonts-freefont-ttf==20211204+svn4273-2 +fonts-gfs-baskerville==1.1-6 +fonts-gfs-porson==1.1-7 +fonts-ipafont-gothic==00303-21ubuntu1 +fonts-liberation==1:2.1.5-3 +fonts-liberation2==1:2.1.5-3 +fonts-lmodern==2.005-1 +fonts-noto-cjk==1:20230817+repack1-3 +fonts-noto-color-emoji==2.047-0ubuntu0.24.04.1 +fonts-opensymbol==4:102.12+LibO24.2.7-0ubuntu0.24.04.4 +fonts-texgyre==20180621-6 +fonts-ubuntu==0.869+git20240321-0ubuntu1 +fonts-urw-base35==20200910-8 +fonts-wqy-zenhei==0.9.45-8 +fuse3==3.14.0-5build1 +g++==4:13.2.0-7ubuntu1 +g++-13==13.3.0-6ubuntu2~24.04.1 +g++-13-x86-64-linux-gnu==13.3.0-6ubuntu2~24.04.1 +g++-x86-64-linux-gnu==4:13.2.0-7ubuntu1 +gawk==1:5.2.1-2build3 +gcc==4:13.2.0-7ubuntu1 +gcc-13==13.3.0-6ubuntu2~24.04.1 +gcc-13-base==13.3.0-6ubuntu2~24.04.1 +gcc-13-x86-64-linux-gnu==13.3.0-6ubuntu2~24.04.1 +gcc-14-base==14.2.0-4ubuntu2~24.04.1 +gcc-x86-64-linux-gnu==4:13.2.0-7ubuntu1 +gdisk==1.0.10-1build1 +gettext-base==0.21-14ubuntu2 +ghostscript==10.02.1~dfsg1-0ubuntu7.8 +gir1.2-girepository-2.0==1.80.1-1 +gir1.2-glib-2.0==2.80.0-6ubuntu3.8 +gir1.2-packagekitglib-1.0==1.2.8-2ubuntu1.4 +git==1:2.43.0-1ubuntu7.3 +git-man==1:2.43.0-1ubuntu7.3 +gnupg==2.4.4-2ubuntu17.4 +gnupg-l10n==2.4.4-2ubuntu17.4 +gnupg-utils==2.4.4-2ubuntu17.4 +gpg==2.4.4-2ubuntu17.4 +gpg-agent==2.4.4-2ubuntu17.4 +gpgconf==2.4.4-2ubuntu17.4 +gpgsm==2.4.4-2ubuntu17.4 +gpgv==2.4.4-2ubuntu17.4 +gpg-wks-client==2.4.4-2ubuntu17.4 +graphviz==2.42.2-9ubuntu0.1 +grep==3.11-4build1 +groff-base==1.23.0-3build2 +gsettings-desktop-schemas==46.1-0ubuntu1 +gtk-update-icon-cache==3.24.41-4ubuntu1.3 +gzip==1.12-1ubuntu3.1 +hicolor-icon-theme==0.17-2 +hostname==3.23+nmu2ubuntu2 +humanity-icon-theme==0.6.16 +imagemagick==8:6.9.12.98+dfsg1-5.2build2 +imagemagick-6.q16==8:6.9.12.98+dfsg1-5.2build2 +imagemagick-6-common==8:6.9.12.98+dfsg1-5.2build2 +info==7.1-3build2 +init==1.66ubuntu1 +init-system-helpers==1.66ubuntu1 +install-info==7.1-3build2 +iproute2==6.1.0-1ubuntu6.2 +iputils-ping==3:20240117-1ubuntu0.1 +iso-codes==4.16.0-1 +java-common==0.75+exp1 +jq==1.7.1-3ubuntu0.24.04.1 +kbd==2.6.4-2ubuntu2 +keyboard-configuration==1.226ubuntu1 +keyboxd==2.4.4-2ubuntu17.4 +kmod==31+20240202-2ubuntu7.1 +krb5-locales==1.20.1-6ubuntu2.6 +landscape-client==24.02-0ubuntu5.7 +landscape-common==24.02-0ubuntu5.7 +latexmk==1:4.83-1 +less==590-2ubuntu2.1 +libabsl20220623t64==20220623.1-3.1ubuntu3.2 +libabw-0.1-1==0.1.3-1build4 +libacl1==2.3.2-1build1.1 +libann0==1.1.2+doc-9build1 +libaom3==3.8.2-2ubuntu0.1 +libapache-pom-java==29-2 +libapparmor1==4.0.1really4.0.1-0ubuntu0.24.04.5 +libappstream5==1.0.2-1build6 +libapt-pkg6.0t64==2.8.3 +libarchive13t64==3.7.2-2ubuntu0.5 +libargon2-1==0~20190702+dfsg-4build1 +libasan8==14.2.0-4ubuntu2~24.04.1 +libasound2-data==1.2.11-1ubuntu0.2 +libasound2t64==1.2.11-1ubuntu0.2 +libass9==1:0.17.1-2build1 +libassuan0==2.5.6-1build1 +libasyncns0==0.8-6build4 +libatk1.0-0t64==2.52.0-1build1 +libatk-bridge2.0-0t64==2.52.0-1build1 +libatm1t64==1:2.5.1-5.1build1 +libatomic1==14.2.0-4ubuntu2~24.04.1 +libatspi2.0-0t64==2.52.0-1build1 +libattr1==1:2.5.2-1build1.1 +libaudit1==1:3.1.2-2.1build1.1 +libaudit-common==1:3.1.2-2.1build1.1 +libavahi-client3==0.8-13ubuntu6.1 +libavahi-common3==0.8-13ubuntu6.1 +libavahi-common-data==0.8-13ubuntu6.1 +libavc1394-0==0.5.4-5build3 +libavcodec60==7:6.1.1-3ubuntu5 +libavdevice60==7:6.1.1-3ubuntu5 +libavfilter9==7:6.1.1-3ubuntu5 +libavformat60==7:6.1.1-3ubuntu5 +libavutil58==7:6.1.1-3ubuntu5 +libbcprov-java==1.77-1 +libbinutils==2.42-4ubuntu2.8 +libblas3==3.12.0-3build1.1 +libblkid1==2.39.3-9ubuntu6.5 +libblkid-dev==2.39.3-9ubuntu6.5 +libbluray2==1:1.3.4-1build1 +libboost-iostreams1.83.0==1.83.0-2.1ubuntu3.2 +libboost-locale1.83.0==1.83.0-2.1ubuntu3.2 +libboost-thread1.83.0==1.83.0-2.1ubuntu3.2 +libbpf1==1:1.3.0-2build2 +libbrotli1==1.1.0-2build2 +libbrotli-dev==1.1.0-2build2 +libbs2b0==3.1.0+dfsg-7build1 +libbsd0==0.12.1-1build1.1 +libbz2-1.0==1.0.8-5.1build0.1 +libbz2-dev==1.0.8-5.1build0.1 +libc6==2.39-0ubuntu8.7 +libc6-dev==2.39-0ubuntu8.7 +libcaca0==0.99.beta20-4ubuntu0.1 +libcairo2==1.18.0-3build1 +libcairo2-dev==1.18.0-3build1 +libcairo-gobject2==1.18.0-3build1 +libcairo-script-interpreter2==1.18.0-3build1 +libcap2==1:2.66-5ubuntu2.2 +libcap2-bin==1:2.66-5ubuntu2.2 +libcap-ng0==0.8.4-2build2 +libc-bin==2.39-0ubuntu8.7 +libcbor0.10==0.10.2-1.2ubuntu2 +libcc1-0==14.2.0-4ubuntu2~24.04.1 +libc-dev-bin==2.39-0ubuntu8.7 +libcdio19t64==2.1.0-4.1ubuntu1.2 +libcdio-cdda2t64==10.2+2.0.1-1.1build2 +libcdio-paranoia2t64==10.2+2.0.1-1.1build2 +libcdr-0.1-1==0.1.7-1build2 +libcdt5==2.42.2-9ubuntu0.1 +libcgraph6==2.42.2-9ubuntu0.1 +libchromaprint1==1.5.1-5 +libcjson1==1.7.17-1 +libclucene-contribs1t64==2.3.3.4+dfsg-1.2ubuntu2 +libclucene-core1t64==2.3.3.4+dfsg-1.2ubuntu2 +libcodec2-1.2==1.2.0-2build1 +libcolamd3==1:7.6.1+dfsg-1build1 +libcolord2==1.4.7-1build2 +libcom-err2==1.47.0-2.4~exp1ubuntu4.1 +libcommons-lang3-java==3.14.0-1 +libcommons-logging-java==1.3.0-1ubuntu1 +libcommons-parent-java==56-1 +libcrypt1==1:4.4.36-4build1 +libcrypt-dev==1:4.4.36-4build1 +libcryptsetup12==2:2.7.0-1ubuntu4.2 +libctf0==2.42-4ubuntu2.8 +libctf-nobfd0==2.42-4ubuntu2.8 +libcups2t64==2.4.7-1.2ubuntu7.9 +libcurl3t64-gnutls==8.5.0-2ubuntu10.8 +libcurl4t64==8.5.0-2ubuntu10.8 +libdatrie1==0.2.13-3build1 +libdav1d7==1.4.1-1build1 +libdb5.3t64==5.3.28+dfsg2-7 +libdbus-1-3==1.14.10-4ubuntu4.1 +libdc1394-25==2.2.6-4build1 +libdconf1==0.40.0-4ubuntu0.1 +libde265-0==1.0.15-1build3 +libdebconfclient0==0.271ubuntu3 +libdecor-0-0==0.2.2-1build2 +libdeflate0==1.19-1build1.1 +libdevmapper1.02.1==2:1.02.185-3ubuntu3.2 +libdouble-conversion3==3.3.0-1build1 +libdpkg-perl==1.22.6ubuntu6.5 +libdrm2==2.4.125-1ubuntu0.1~24.04.1 +libdrm-amdgpu1==2.4.125-1ubuntu0.1~24.04.1 +libdrm-common==2.4.125-1ubuntu0.1~24.04.1 +libdrm-intel1==2.4.125-1ubuntu0.1~24.04.1 +libduktape207==2.7.0+tests-0ubuntu3 +libdw1t64==0.190-1.1ubuntu0.1 +libeatmydata1==131-1ubuntu1 +libe-book-0.1-1==0.1.3-2build6 +libedit2==3.1-20230828-1build1 +libegl1==1.7.0-1build1 +libegl-mesa0==25.2.8-0ubuntu0.24.04.1 +libelf1t64==0.190-1.1ubuntu0.1 +libeot0==0.01-5build3 +libepoxy0==1.5.10-1build1 +libepubgen-0.1-1==0.1.1-1ubuntu6 +liberror-perl==0.17029-2 +libestr0==0.1.11-1build1 +libetonyek-0.1-1==0.1.10-5build1 +libevdev2==1.13.1+dfsg-1build1 +libevent-core-2.1-7t64==2.1.12-stable-9ubuntu2 +libexpat1==2.6.1-2ubuntu0.4 +libexpat1-dev==2.6.1-2ubuntu0.4 +libext2fs2t64==1.47.0-2.4~exp1ubuntu4.1 +libexttextcat-2.0-0==3.4.7-1build1 +libexttextcat-data==3.4.7-1build1 +libfastjson4==1.2304.0-1build1 +libfdisk1==2.39.3-9ubuntu6.5 +libffi8==3.4.6-1build1 +libffi-dev==3.4.6-1build1 +libfftw3-double3==3.3.10-1ubuntu3 +libfido2-1==1.14.0-1build3 +libflac12t64==1.4.3+ds-2.1ubuntu2 +libflite1==2.2-6build3 +libfontbox-java==1:1.8.16-5 +libfontconfig1==2.15.0-1.1ubuntu2 +libfontconfig-dev==2.15.0-1.1ubuntu2 +libfontenc1==1:1.1.8-1build1 +libfreehand-0.1-1==0.1.2-3build3 +libfreetype6==2.13.2+dfsg-1ubuntu0.1 +libfreetype-dev==2.13.2+dfsg-1ubuntu0.1 +libfribidi0==1.0.13-3build1 +libfuse3-3==3.14.0-5build1 +libgbm1==25.2.8-0ubuntu0.24.04.1 +libgcc-13-dev==13.3.0-6ubuntu2~24.04.1 +libgcc-s1==14.2.0-4ubuntu2~24.04.1 +libgcrypt20==1.10.3-2build1 +libgd3==2.3.3-9ubuntu5 +libgdbm6t64==1.23-5.1build1 +libgdbm-compat4t64==1.23-5.1build1 +libgdk-pixbuf-2.0-0==2.42.10+dfsg-3ubuntu3.2 +libgdk-pixbuf2.0-bin==2.42.10+dfsg-3ubuntu3.2 +libgdk-pixbuf2.0-common==2.42.10+dfsg-3ubuntu3.2 +libgfortran5==14.2.0-4ubuntu2~24.04.1 +libgif7==5.2.2-1ubuntu1 +libgirepository-1.0-1==1.80.1-1 +libgirepository-2.0-0==2.80.0-6ubuntu3.8 +libgl1==1.7.0-1build1 +libgl1-mesa-dri==25.2.8-0ubuntu0.24.04.1 +libgles2==1.7.0-1build1 +libglib2.0-0t64==2.80.0-6ubuntu3.8 +libglib2.0-bin==2.80.0-6ubuntu3.8 +libglib2.0-data==2.80.0-6ubuntu3.8 +libglib2.0-dev==2.80.0-6ubuntu3.8 +libglib2.0-dev-bin==2.80.0-6ubuntu3.8 +libglvnd0==1.7.0-1build1 +libglx0==1.7.0-1build1 +libglx-mesa0==25.2.8-0ubuntu0.24.04.1 +libgme0==0.6.3-7build1 +libgmp10==2:6.3.0+dfsg-2ubuntu6.1 +libgnutls30t64==3.8.3-1.1ubuntu3.4 +libgomp1==14.2.0-4ubuntu2~24.04.1 +libgpg-error0==1.47-3build2.1 +libgpg-error-l10n==1.47-3build2.1 +libgpgme11t64==1.18.0-4.1ubuntu4 +libgpgmepp6t64==1.18.0-4.1ubuntu4 +libgpm2==1.20.7-11 +libgprofng0==2.42-4ubuntu2.8 +libgraphene-1.0-0==1.10.8-3build2 +libgraphite2-3==1.3.14-2build1 +libgs10==10.02.1~dfsg1-0ubuntu7.8 +libgs10-common==10.02.1~dfsg1-0ubuntu7.8 +libgs-common==10.02.1~dfsg1-0ubuntu7.8 +libgsm1==1.0.22-1build1 +libgssapi-krb5-2==1.20.1-6ubuntu2.6 +libgstreamer1.0-0==1.24.2-1ubuntu0.1 +libgstreamer-plugins-base1.0-0==1.24.2-1ubuntu0.3 +libgtk-3-0t64==3.24.41-4ubuntu1.3 +libgtk-3-bin==3.24.41-4ubuntu1.3 +libgtk-3-common==3.24.41-4ubuntu1.3 +libgtk-4-1==4.14.5+ds-0ubuntu0.9 +libgtk-4-common==4.14.5+ds-0ubuntu0.9 +libgts-0.7-5t64==0.7.6+darcs121130-5.2build1 +libgudev-1.0-0==1:238-5ubuntu1 +libgvc6==2.42.2-9ubuntu0.1 +libgvpr2==2.42.2-9ubuntu0.1 +libharfbuzz0b==8.3.0-2build2 +libharfbuzz-icu0==8.3.0-2build2 +libheif1==1.17.6-1ubuntu4.2 +libheif-plugin-aomdec==1.17.6-1ubuntu4.2 +libheif-plugin-libde265==1.17.6-1ubuntu4.2 +libhogweed6t64==3.9.1-2.2build1.1 +libhunspell-1.7-0==1.7.2+really1.7.2-10build3 +libhwasan0==14.2.0-4ubuntu2~24.04.1 +libhwy1t64==1.0.7-8.1build1 +libhyphen0==2.8.8-7build3 +libice6==2:1.0.10-1build3 +libice-dev==2:1.0.10-1build3 +libicu74==74.2-1ubuntu3.1 +libidn12==1.42-1build1 +libidn2-0==2.3.7-2build1.1 +libiec61883-0==1.2.0-6build1 +libijs-0.35==0.35-15.1build1 +libinput10==1.25.0-1ubuntu3.3 +libinput-bin==1.25.0-1ubuntu3.3 +libisl23==0.26-3build1.1 +libitm1==14.2.0-4ubuntu2~24.04.1 +libjack-jackd2-0==1.9.21~dfsg-3ubuntu3 +libjansson4==2.14-2build2 +libjbig0==2.1-6.1ubuntu2 +libjbig2dec0==0.20-1build3 +libjpeg8==8c-2ubuntu11 +libjpeg-turbo8==2.1.5-2ubuntu2 +libjq1==1.7.1-3ubuntu0.24.04.1 +libjs-jquery==3.6.1+dfsg+~3.5.14-1 +libjson-c5==0.17-1build1 +libjs-sphinxdoc==7.2.6-6 +libjs-underscore==1.13.4~dfsg+~1.11.4-3 +libjxl0.7==0.7.0-10.2ubuntu6.1 +libk5crypto3==1.20.1-6ubuntu2.6 +libkeyutils1==1.6.3-3build1 +libkmod2==31+20240202-2ubuntu7.1 +libkpathsea6==2023.20230311.66589-9build3 +libkrb5-3==1.20.1-6ubuntu2.6 +libkrb5support0==1.20.1-6ubuntu2.6 +libksba8==1.6.6-1build1 +liblab-gamut1==2.42.2-9ubuntu0.1 +liblangtag1==0.6.7-1build2 +liblangtag-common==0.6.7-1build2 +liblapack3==3.12.0-3build1.1 +liblcms2-2==2.14-2build1 +libldap2==2.6.10+dfsg-0ubuntu0.24.04.1 +libldap-common==2.6.10+dfsg-0ubuntu0.24.04.1 +liblept5==1.82.0-3build4 +liblerc4==4.0.0+ds-4ubuntu2 +liblibreoffice-java==4:24.2.7-0ubuntu0.24.04.4 +liblilv-0-0==0.24.22-1build1 +libllvm20==1:20.1.2-0ubuntu1~24.04.2 +liblocale-gettext-perl==1.07-6ubuntu5 +liblqr-1-0==0.4.2-2.1build2 +liblsan0==14.2.0-4ubuntu2~24.04.1 +libltdl7==2.4.7-7build1 +liblua5.4-0==5.4.6-3build2 +liblz4-1==1.9.4-1build1.1 +liblzma5==5.6.1+really5.4.5-1ubuntu0.2 +liblzo2-2==2.10-2build4 +libmagic1t64==1:5.45-3build1 +libmagickcore-6.q16-7t64==8:6.9.12.98+dfsg1-5.2build2 +libmagickwand-6.q16-7t64==8:6.9.12.98+dfsg1-5.2build2 +libmagic-mgc==1:5.45-3build1 +libmbedcrypto7t64==2.28.8-1 +libmd0==1.1.0-2build1.1 +libmd4c0==0.4.8-1build1 +libmhash2==0.9.9.9-9build3 +libmnl0==1.0.5-2build1 +libmount1==2.39.3-9ubuntu6.5 +libmount-dev==2.39.3-9ubuntu6.5 +libmp3lame0==3.100-6build1 +libmpc3==1.3.1-1build1.1 +libmpfr6==4.2.1-1build1.1 +libmpg123-0t64==1.32.5-1ubuntu1.1 +libmspub-0.1-1==0.1.4-3build7 +libmtdev1t64==1.1.6-1.1build1 +libmwaw-0.3-3==0.3.22-1build1 +libmysofa1==1.3.2+dfsg-2ubuntu2 +libmythes-1.2-0==2:1.2.5-1build1 +libncursesw6==6.4+20240113-1ubuntu2 +libnetplan1==1.1.2-8ubuntu1~24.04.1 +libnettle8t64==3.9.1-2.2build1.1 +libnewt0.52==0.52.24-2ubuntu2 +libnghttp2-14==1.59.0-1ubuntu0.2 +libnorm1t64==1.5.9+dfsg-3.1build1 +libnpth0t64==1.6-3.1build1 +libnspr4==2:4.35-1.1build1 +libnss3==2:3.98-1ubuntu0.1 +libnss3-tools==2:3.98-1ubuntu0.1 +libnss-systemd==255.4-1ubuntu8.12 +libnuma1==2.0.18-1ubuntu0.24.04.1 +libodfgen-0.1-1==0.1.8-2build3 +libogg0==1.3.5-3build1 +libonig5==6.9.9-1build1 +libopenal1==1:1.23.1-4build1 +libopenal-data==1:1.23.1-4build1 +libopenjp2-7==2.5.0-2ubuntu0.4 +libopenmpt0t64==0.7.3-1.1build3 +libopus0==1.4-1build1 +liborc-0.4-0t64==1:0.4.38-1ubuntu0.1 +liborcus-0.18-0==0.19.2-3build3 +liborcus-parser-0.18-0==0.19.2-3build3 +libp11-kit0==0.25.3-4ubuntu2.1 +libpackagekit-glib2-18==1.2.8-2ubuntu1.4 +libpagemaker-0.0-0==0.0.4-1build4 +libpam0g==1.5.3-5ubuntu5.5 +libpam-cap==1:2.66-5ubuntu2.2 +libpam-modules==1.5.3-5ubuntu5.5 +libpam-modules-bin==1.5.3-5ubuntu5.5 +libpam-runtime==1.5.3-5ubuntu5.5 +libpam-systemd==255.4-1ubuntu8.12 +libpango-1.0-0==1.52.1+ds-1build1 +libpangocairo-1.0-0==1.52.1+ds-1build1 +libpangoft2-1.0-0==1.52.1+ds-1build1 +libpaper1==1.1.29build1 +libpaper-utils==1.1.29build1 +libpathplan4==2.42.2-9ubuntu0.1 +libpciaccess0==0.17-3ubuntu0.24.04.2 +libpcre2-16-0==10.42-4ubuntu2.1 +libpcre2-32-0==10.42-4ubuntu2.1 +libpcre2-8-0==10.42-4ubuntu2.1 +libpcre2-dev==10.42-4ubuntu2.1 +libpcre2-posix3==10.42-4ubuntu2.1 +libpcsclite1==2.0.3-1build1 +libpdfbox-java==1:1.8.16-5 +libperl5.38t64==5.38.2-3.2ubuntu0.2 +libpgm-5.3-0t64==5.3.128~dfsg-2.1build1 +libpipeline1==1.5.7-2 +libpixman-1-0==0.42.2-1build1 +libpixman-1-dev==0.42.2-1build1 +libpkgconf3==1.8.1-2build1 +libplacebo338==6.338.2-2build1 +libpng16-16t64==1.6.43-5ubuntu0.5 +libpng-dev==1.6.43-5ubuntu0.5 +libpocketsphinx3==0.8.0+real5prealpha+1-15ubuntu5 +libpolkit-agent-1-0==124-2ubuntu1.24.04.2 +libpolkit-gobject-1-0==124-2ubuntu1.24.04.2 +libpoppler134==24.02.0-1ubuntu9.8 +libpopt0==1.19+dfsg-1build1 +libpostproc57==7:6.1.1-3ubuntu5 +libpotrace0==1.16-2build1 +libproc2-0==2:4.0.4-4ubuntu3.2 +libpsl5t64==0.21.2-1.1build1 +libptexenc1==2023.20230311.66589-9build3 +libpthread-stubs0-dev==0.4-1build3 +libpulse0==1:16.1+dfsg1-2ubuntu10.1 +libpython3.12-dev==3.12.3-1ubuntu0.12 +libpython3.12-minimal==3.12.3-1ubuntu0.12 +libpython3.12-stdlib==3.12.3-1ubuntu0.12 +libpython3.12t64==3.12.3-1ubuntu0.12 +libpython3-dev==3.12.3-0ubuntu2.1 +libpython3-stdlib==3.12.3-0ubuntu2.1 +libqpdf29t64==11.9.0-1.1ubuntu0.1 +libqt5core5t64==5.15.13+dfsg-1ubuntu1 +libqt5dbus5t64==5.15.13+dfsg-1ubuntu1 +libqt5gui5t64==5.15.13+dfsg-1ubuntu1 +libqt5network5t64==5.15.13+dfsg-1ubuntu1 +libqt5positioning5==5.15.13+dfsg-1 +libqt5printsupport5t64==5.15.13+dfsg-1ubuntu1 +libqt5qml5==5.15.13+dfsg-1ubuntu0.1 +libqt5qmlmodels5==5.15.13+dfsg-1ubuntu0.1 +libqt5quick5==5.15.13+dfsg-1ubuntu0.1 +libqt5sensors5==5.15.13-1 +libqt5svg5==5.15.13-1 +libqt5webchannel5==5.15.13-1 +libqt5webkit5==5.212.0~alpha4-36 +libqt5widgets5t64==5.15.13+dfsg-1ubuntu1 +libquadmath0==14.2.0-4ubuntu2~24.04.1 +librabbitmq4==0.11.0-1build2 +libraptor2-0==2.0.16-3ubuntu0.1 +librasqal3t64==0.9.33-2.1build1 +librav1e0==0.7.1-2 +libraw1394-11==2.1.2-2build3 +libraw23t64==0.21.2-2.1ubuntu0.24.04.1 +librdf0t64==1.0.17-3.1ubuntu3 +libreadline8t64==8.2-4build1 +libreoffice-base-core==4:24.2.7-0ubuntu0.24.04.4 +libreoffice-calc==4:24.2.7-0ubuntu0.24.04.4 +libreoffice-common==4:24.2.7-0ubuntu0.24.04.4 +libreoffice-core==4:24.2.7-0ubuntu0.24.04.4 +libreoffice-draw==4:24.2.7-0ubuntu0.24.04.4 +libreoffice-impress==4:24.2.7-0ubuntu0.24.04.4 +libreoffice-java-common==4:24.2.7-0ubuntu0.24.04.4 +libreoffice-style-colibre==4:24.2.7-0ubuntu0.24.04.4 +libreoffice-uiconfig-calc==4:24.2.7-0ubuntu0.24.04.4 +libreoffice-uiconfig-common==4:24.2.7-0ubuntu0.24.04.4 +libreoffice-uiconfig-draw==4:24.2.7-0ubuntu0.24.04.4 +libreoffice-uiconfig-impress==4:24.2.7-0ubuntu0.24.04.4 +libreoffice-uiconfig-writer==4:24.2.7-0ubuntu0.24.04.4 +libreoffice-writer==4:24.2.7-0ubuntu0.24.04.4 +librevenge-0.0-0==0.0.5-3build1 +librist4==0.2.10+dfsg-2 +librsvg2-2==2.58.0+dfsg-1build1 +librsvg2-common==2.58.0+dfsg-1build1 +librtmp1==2.4+20151223.gitfa8646d.1-2build7 +librubberband2==3.3.0+dfsg-2build1 +libsamplerate0==0.2.2-4build1 +libsasl2-2==2.1.28+dfsg1-5ubuntu3.1 +libsasl2-modules==2.1.28+dfsg1-5ubuntu3.1 +libsasl2-modules-db==2.1.28+dfsg1-5ubuntu3.1 +libsdl2-2.0-0==2.30.0+dfsg-1ubuntu3.1 +libseccomp2==2.5.5-1ubuntu3.1 +libselinux1==3.5-2ubuntu2.1 +libselinux1-dev==3.5-2ubuntu2.1 +libsemanage2==3.5-1build5 +libsemanage-common==3.5-1build5 +libsensors5==1:3.6.0-9build1 +libsensors-config==1:3.6.0-9build1 +libsepol2==3.5-2build1 +libsepol-dev==3.5-2build1 +libserd-0-0==0.32.2-1 +libsframe1==2.42-4ubuntu2.8 +libsharpyuv0==1.3.2-0.4build3 +libshine3==3.1.1-2build1 +libsigsegv2==2.14-1ubuntu2 +libslang2==2.3.3-3build2 +libsm6==2:1.2.3-1build3 +libsmartcols1==2.39.3-9ubuntu6.5 +libsm-dev==2:1.2.3-1build3 +libsnappy1v5==1.1.10-1build1 +libsndfile1==1.2.2-1ubuntu5.24.04.1 +libsndio7.0==1.9.0-0.3build3 +libsodium23==1.0.18-1ubuntu0.24.04.1 +libsord-0-0==0.16.16-2build1 +libsoxr0==0.1.3-4build3 +libspeex1==1.2.1-2ubuntu2.24.04.1 +libsphinxbase3t64==0.8+5prealpha+1-17build2 +libsqlite3-0==3.45.1-1ubuntu2.5 +libsratom-0-0==0.6.16-1build1 +libsrt1.5-gnutls==1.5.3-1build2 +libss2==1.47.0-2.4~exp1ubuntu4.1 +libssh-4==0.10.6-2ubuntu0.4 +libssh-gcrypt-4==0.10.6-2ubuntu0.4 +libssl3t64==3.0.13-0ubuntu3.7 +libstdc++-13-dev==13.3.0-6ubuntu2~24.04.1 +libstdc++6==14.2.0-4ubuntu2~24.04.1 +libstemmer0d==2.2.0-4build1 +libsuitesparseconfig7==1:7.6.1+dfsg-1build1 +libsvtav1enc1d1==1.7.0+dfsg-2build1 +libswresample4==7:6.1.1-3ubuntu5 +libswscale7==7:6.1.1-3ubuntu5 +libsynctex2==2023.20230311.66589-9build3 +libsystemd0==255.4-1ubuntu8.12 +libsystemd-shared==255.4-1ubuntu8.12 +libtasn1-6==4.19.0-3ubuntu0.24.04.2 +libteckit0==2.5.12+ds1-1 +libtesseract5==5.3.4-1build5 +libtexlua53-5==2023.20230311.66589-9build3 +libtext-charwidth-perl==0.04-11build3 +libtext-iconv-perl==1.7-8build3 +libtext-wrapi18n-perl==0.06-10 +libthai0==0.1.29-2build1 +libthai-data==0.1.29-2build1 +libtheora0==1.1.1+dfsg.1-16.1build3 +libtiff6==4.5.1+git230720-4ubuntu2.4 +libtinfo6==6.4+20240113-1ubuntu2 +libtirpc3t64==1.3.4+ds-1.1build1 +libtirpc-common==1.3.4+ds-1.1build1 +libtsan2==14.2.0-4ubuntu2~24.04.1 +libtwolame0==0.4.0-2build3 +libubsan1==14.2.0-4ubuntu2~24.04.1 +libuchardet0==0.0.8-1build1 +libudev1==255.4-1ubuntu8.12 +libudfread0==1.1.2-1build1 +libunibreak5==5.1-2build1 +libunistring5==1.1-2build1.1 +libuno-cppu3t64==4:24.2.7-0ubuntu0.24.04.4 +libuno-cppuhelpergcc3-3t64==4:24.2.7-0ubuntu0.24.04.4 +libunoloader-java==4:24.2.7-0ubuntu0.24.04.4 +libuno-purpenvhelpergcc3-3t64==4:24.2.7-0ubuntu0.24.04.4 +libuno-sal3t64==4:24.2.7-0ubuntu0.24.04.4 +libuno-salhelpergcc3-3t64==4:24.2.7-0ubuntu0.24.04.4 +libunwind8==1.6.2-3build1.1 +libusb-1.0-0==2:1.0.27-1 +libutempter0==1.2.1-3build1 +libuuid1==2.39.3-9ubuntu6.5 +libva2==2.20.0-2ubuntu0.1 +libva-drm2==2.20.0-2ubuntu0.1 +libva-x11-2==2.20.0-2ubuntu0.1 +libvdpau1==1.5-2build1 +libvidstab1.1==1.1.0-2build1 +libvisio-0.1-1==0.1.7-1build9 +libvorbis0a==1.3.7-1build3 +libvorbisenc2==1.3.7-1build3 +libvorbisfile3==1.3.7-1build3 +libvpl2==2023.3.0-1build1 +libvpx9==1.14.0-1ubuntu2.3 +libvulkan1==1.3.275.0-1build1 +libwacom9==2.10.0-2 +libwacom-common==2.10.0-2 +libwayland-client0==1.22.0-2.1build1 +libwayland-cursor0==1.22.0-2.1build1 +libwayland-egl1==1.22.0-2.1build1 +libwebp7==1.3.2-0.4build3 +libwebpdemux2==1.3.2-0.4build3 +libwebpmux3==1.3.2-0.4build3 +libwoff1==1.0.2-2build1 +libwpd-0.10-10==0.10.3-2build2 +libwpg-0.3-3==0.3.4-3build1 +libwps-0.4-4==0.4.14-2build1 +libx11-6==2:1.8.7-1build1 +libx11-data==2:1.8.7-1build1 +libx11-dev==2:1.8.7-1build1 +libx11-xcb1==2:1.8.7-1build1 +libx264-164==2:0.164.3108+git31e19f9-1 +libx265-199==3.5-2build1 +libxau6==1:1.0.9-1build6 +libxau-dev==1:1.0.9-1build6 +libxaw7==2:1.0.14-1build2 +libxcb1==1.15-1ubuntu2 +libxcb1-dev==1.15-1ubuntu2 +libxcb-dri3-0==1.15-1ubuntu2 +libxcb-glx0==1.15-1ubuntu2 +libxcb-icccm4==0.4.1-1.1build3 +libxcb-image0==0.4.0-2build1 +libxcb-keysyms1==0.4.0-1build4 +libxcb-present0==1.15-1ubuntu2 +libxcb-randr0==1.15-1ubuntu2 +libxcb-render0==1.15-1ubuntu2 +libxcb-render0-dev==1.15-1ubuntu2 +libxcb-render-util0==0.3.9-1build4 +libxcb-shape0==1.15-1ubuntu2 +libxcb-shm0==1.15-1ubuntu2 +libxcb-shm0-dev==1.15-1ubuntu2 +libxcb-sync1==1.15-1ubuntu2 +libxcb-util1==0.4.0-1build3 +libxcb-xfixes0==1.15-1ubuntu2 +libxcb-xinerama0==1.15-1ubuntu2 +libxcb-xinput0==1.15-1ubuntu2 +libxcb-xkb1==1.15-1ubuntu2 +libxcomposite1==1:0.4.5-1build3 +libxcursor1==1:1.2.1-1build1 +libxdamage1==1:1.1.6-1build1 +libxdmcp6==1:1.1.3-0ubuntu6 +libxdmcp-dev==1:1.1.3-0ubuntu6 +libxext6==2:1.3.4-1build2 +libxext-dev==2:1.3.4-1build2 +libxfixes3==1:6.0.0-2build1 +libxfont2==1:2.0.6-1build1 +libxi6==2:1.8.1-1build1 +libxinerama1==2:1.1.4-3build1 +libxkbcommon0==1.6.0-1build1 +libxkbcommon-x11-0==1.6.0-1build1 +libxkbfile1==1:1.1.0-1build4 +libxml2==2.9.14+dfsg-1.3ubuntu3.7 +libxmlb2==0.3.18-1 +libxmlsec1t64==1.2.39-5build2 +libxmlsec1t64-nss==1.2.39-5build2 +libxmu6==2:1.1.3-3build2 +libxmuu1==2:1.1.3-3build2 +libxpm4==1:3.5.17-1build2 +libxrandr2==2:1.5.2-2build1 +libxrender1==1:0.9.10-1.1build1 +libxrender-dev==1:0.9.10-1.1build1 +libxshmfence1==1.3-1build5 +libxslt1.1==1.1.39-0exp1ubuntu0.24.04.3 +libxss1==1:1.2.3-1build3 +libxt6t64==1:1.2.1-1.2build1 +libxtables12==1.8.10-3ubuntu2 +libxtst6==2:1.2.3-1.1build1 +libxv1==2:1.0.11-1.1build1 +libxvidcore4==2:1.3.7-1build1 +libxxf86vm1==1:1.1.4-1build4 +libxxhash0==0.8.2-2build1 +libyajl2==2.1.0-5build1 +libyaml-0-2==0.2.5-1build1 +libzimg2==3.0.5+ds1-1build1 +libzix-0-0==0.4.2-2build1 +libzmq5==4.3.5-1build2 +libzstd1==1.5.5+dfsg2-2build1.1 +libzvbi0t64==0.2.42-2 +libzvbi-common==0.2.42-2 +libzzip-0-13t64==0.13.72+dfsg.1-1.2build1 +linux-libc-dev==6.8.0-106.106 +locales==2.39-0ubuntu8.7 +login==1:4.13+dfsg1-4ubuntu3.2 +logrotate==3.21.0-2build1 +logsave==1.47.0-2.4~exp1ubuntu4.1 +lp-solve==5.5.2.5-2ubuntu0.1 +lsb-release==12.0-2 +lshw==02.19.git.2021.06.19.996aaad9c7-2build3 +lsof==4.95.0-1build3 +lto-disabled-list==47 +make==4.3-4.1build2 +man-db==2.12.0-4build2 +manpages==6.7-2 +mawk==1.3.4.20240123-1build1 +media-types==10.1.0 +mesa-libgallium==25.2.8-0ubuntu0.24.04.1 +mesa-vulkan-drivers==25.2.8-0ubuntu0.24.04.1 +motd-news-config==13ubuntu10.4 +mount==2.39.3-9ubuntu6.5 +nano==7.2-2ubuntu0.1 +ncurses-base==6.4+20240113-1ubuntu2 +ncurses-bin==6.4+20240113-1ubuntu2 +netbase==6.4 +netcat-openbsd==1.226-1ubuntu2 +netplan.io==1.1.2-8ubuntu1~24.04.1 +netplan-generator==1.1.2-8ubuntu1~24.04.1 +networkd-dispatcher==2.2.4-1 +nodejs==22.22.1-1nodesource1 +ocl-icd-libopencl1==2.3.2-1build1 +openjdk-21-jre-headless==21.0.10+7-1~24.04 +openssh-client==1:9.6p1-3ubuntu13.14 +openssl==3.0.13-0ubuntu3.7 +packagekit==1.2.8-2ubuntu1.4 +packagekit-tools==1.2.8-2ubuntu1.4 +pandoc==3.1.3+ds-2 +pandoc-data==3.1.3-1 +passwd==1:4.13+dfsg1-4ubuntu3.2 +pastebinit==1.6.2-1 +patch==2.7.6-7build3 +pci.ids==0.0~2024.03.31-1ubuntu0.1 +pdftk-java==3.3.3-2 +perl==5.38.2-3.2ubuntu0.2 +perl-base==5.38.2-3.2ubuntu0.2 +perl-modules-5.38==5.38.2-3.2ubuntu0.2 +pinentry-curses==1.2.1-3ubuntu5 +pipx==1.4.3-1 +pkgconf==1.8.1-2build1 +pkgconf-bin==1.8.1-2build1 +pkg-config==1.8.1-2build1 +polkitd==124-2ubuntu1.24.04.2 +poppler-data==0.4.12-1 +poppler-utils==24.02.0-1ubuntu9.8 +preview-latex-style==13.2-1 +procps==2:4.0.4-4ubuntu3.2 +psmisc==23.7-1build1 +publicsuffix==20231001.0357-0.1 +python3==3.12.3-0ubuntu2.1 +python3.12==3.12.3-1ubuntu0.12 +python3.12-dev==3.12.3-1ubuntu0.12 +python3.12-minimal==3.12.3-1ubuntu0.12 +python3.12-venv==3.12.3-1ubuntu0.12 +python3-apport==2.28.1-0ubuntu3.8 +python3-apt==2.7.7ubuntu5.2 +python3-argcomplete==3.1.4-1ubuntu0.1 +python3-attr==23.2.0-2 +python3-automat==22.10.0-2 +python3-babel==2.10.3-3build1 +python3-bcrypt==3.2.2-1build1 +python3-blinker==1.7.0-1 +python3-certifi==2023.11.17-1 +python3-cffi-backend==1.16.0-2build1 +python3-chardet==5.2.0+dfsg-1 +python3-click==8.1.6-2 +python3-colorama==0.4.6-4 +python3-commandnotfound==23.04.0 +python3-configobj==5.0.8-3 +python3-constantly==23.10.4-1 +python3-cryptography==41.0.7-4ubuntu0.1 +python3-dbus==1.3.2-5build3 +python3-debconf==1.5.86ubuntu1 +python3-dev==3.12.3-0ubuntu2.1 +python3-distro==1.9.0-1 +python3-distro-info==1.7build1 +python3-distupgrade==1:24.04.28 +python3-gdbm==3.12.3-0ubuntu1 +python3-gi==3.48.2-1 +python3-hamcrest==2.1.0-1 +python3-httplib2==0.20.4-3 +python3-hyperlink==21.0.0-5 +python3-idna==3.6-2ubuntu0.1 +python3-incremental==22.10.0-1 +python3-jinja2==3.1.2-1ubuntu1.3 +python3-jsonpatch==1.32-3 +python3-json-pointer==2.0-0ubuntu1 +python3-jsonschema==4.10.3-2ubuntu1 +python3-jwt==2.7.0-1 +python3-launchpadlib==1.11.0-6 +python3-lazr.restfulclient==0.14.6-1 +python3-lazr.uri==1.0.6-3 +python3-markdown-it==3.0.0-2 +python3-markupsafe==2.1.5-1build2 +python3-mdurl==0.1.2-1 +python3-minimal==3.12.3-0ubuntu2.1 +python3-netifaces==0.11.0-2build3 +python3-netplan==1.1.2-8ubuntu1~24.04.1 +python3-newt==0.52.24-2ubuntu2 +python3-oauthlib==3.2.2-1 +python3-openssl==23.2.0-1 +python3-packaging==24.0-1 +python3-pip==24.0+dfsg-1ubuntu1.3 +python3-pip-whl==24.0+dfsg-1ubuntu1.3 +python3-pkg-resources==68.1.2-2ubuntu1.2 +python3-platformdirs==4.2.0-1 +python3-problem-report==2.28.1-0ubuntu3.8 +python3-pyasn1==0.4.8-4ubuntu0.1 +python3-pyasn1-modules==0.2.8-1 +python3-pycurl==7.45.3-1build2 +python3-pygments==2.17.2+dfsg-1 +python3-pyparsing==3.1.1-1 +python3-pyrsistent==0.20.0-1build2 +python3-requests==2.31.0+dfsg-1ubuntu1.1 +python3-rich==13.7.1-1 +python3-serial==3.5-2 +python3-service-identity==24.1.0-1 +python3-setuptools==68.1.2-2ubuntu1.2 +python3-setuptools-whl==68.1.2-2ubuntu1.2 +python3-six==1.16.0-4 +python3-software-properties==0.99.49.4 +python3-systemd==235-1build4 +python3-twisted==24.3.0-1ubuntu0.1 +python3-typing-extensions==4.10.0-1 +python3-tz==2024.1-2 +python3-update-manager==1:24.04.12 +python3-urllib3==2.0.7-1ubuntu0.6 +python3-userpath==1.9.1-1 +python3-venv==3.12.3-0ubuntu2.1 +python3-wadllib==1.3.6-5 +python3-wheel==0.42.0-2 +python3-yaml==6.0.1-2build2 +python3-zope.interface==6.1-1build1 +python-apt-common==2.7.7ubuntu5.2 +python-babel-localedata==2.10.3-3build1 +qpdf==11.9.0-1.1ubuntu0.1 +readline-common==8.2-4build1 +ripgrep==14.1.0-1 +rpcsvc-proto==1.4.2-0ubuntu7 +rsync==3.2.7-1ubuntu1.2 +rsyslog==8.2312.0-3ubuntu9.1 +run-one==1.17-0ubuntu2 +sed==4.9-2build1 +sensible-utils==0.0.22 +session-migration==0.3.9build1 +sgml-base==1.31 +shared-mime-info==2.4-4 +show-motd==3.12 +snapd==2.73+ubuntu24.04 +software-properties-common==0.99.49.4 +sqlite3==3.45.1-1ubuntu2.5 +squashfs-tools==1:4.6.1-1build1 +sudo==1.9.15p5-3ubuntu5.24.04.1 +systemd==255.4-1ubuntu8.12 +systemd-dev==255.4-1ubuntu8.12 +systemd-hwe-hwdb==255.1.6 +systemd-resolved==255.4-1ubuntu8.12 +systemd-sysv==255.4-1ubuntu8.12 +systemd-timesyncd==255.4-1ubuntu8.12 +sysvinit-utils==3.08-6ubuntu3 +t1utils==1.41-4build3 +tar==1.35+dfsg-3build1 +teckit==2.5.12+ds1-1 +tesseract-ocr==5.3.4-1build5 +tesseract-ocr-eng==1:4.1.0-2 +tesseract-ocr-osd==1:4.1.0-2 +tex-common==6.18 +texlive-base==2023.20240207-1 +texlive-binaries==2023.20230311.66589-9build3 +texlive-fonts-recommended==2023.20240207-1 +texlive-lang-greek==2023.20240207-1 +texlive-latex-base==2023.20240207-1 +texlive-latex-extra==2023.20240207-1 +texlive-latex-recommended==2023.20240207-1 +texlive-pictures==2023.20240207-1 +texlive-science==2023.20240207-1 +texlive-xetex==2023.20240207-1 +time==1.9-0.2build1 +tipa==2:1.3-21 +tmux==3.4-1ubuntu0.1 +tree==2.1.1-2ubuntu3.24.04.2 +tzdata==2025b-0ubuntu0.24.04.1 +ubuntu-keyring==2023.11.28.1 +ubuntu-minimal==1.539.2 +ubuntu-mono==24.04-0ubuntu1 +ubuntu-pro-client==37.1ubuntu0~24.04 +ubuntu-pro-client-l10n==37.1ubuntu0~24.04 +ubuntu-release-upgrader-core==1:24.04.28 +ubuntu-wsl==1.539.2 +ucf==3.0043+nmu1 +udev==255.4-1ubuntu8.12 +unattended-upgrades==2.9.1+nmu4ubuntu1 +uno-libs-private==4:24.2.7-0ubuntu0.24.04.4 +unzip==6.0-28ubuntu4.1 +update-manager-core==1:24.04.12 +update-motd==3.12 +ure==4:24.2.7-0ubuntu0.24.04.4 +ure-java==4:24.2.7-0ubuntu0.24.04.4 +usb.ids==2024.03.18-1 +util-linux==2.39.3-9ubuntu6.5 +uuid-dev==2.39.3-9ubuntu6.5 +uuid-runtime==2.39.3-9ubuntu6.5 +vim==2:9.1.0016-1ubuntu7.9 +vim-common==2:9.1.0016-1ubuntu7.9 +vim-runtime==2:9.1.0016-1ubuntu7.9 +vim-tiny==2:9.1.0016-1ubuntu7.9 +wget==1.21.4-1ubuntu4.1 +whiptail==0.52.24-2ubuntu2 +wkhtmltopdf==0.12.6-2build2 +wsl-pro-service==0.1.18~24.04.3 +wsl-setup==0.5.10~24.04.1 +x11-common==1:7.7+23ubuntu3 +x11proto-core-dev==2023.2-1 +x11proto-dev==2023.2-1 +x11-xkb-utils==7.7+8build2 +xauth==1:1.1.2-1build1 +xdg-user-dirs==0.18-1build1 +xdg-utils==1.1.3-4.1ubuntu3 +xfonts-cyrillic==1:1.0.5+nmu1 +xfonts-encodings==1:1.0.5-0ubuntu2 +xfonts-scalable==1:1.0.3-1.3 +xfonts-utils==1:7.7+6build3 +xkb-data==2.41-2ubuntu1.1 +xml-core==0.19 +xorg-sgml-doctools==1:1.11-1.1 +xserver-common==2:21.1.12-1ubuntu1.5 +xtrans-dev==1.4.0-1 +xvfb==2:21.1.12-1ubuntu1.5 +xxd==2:9.1.0016-1ubuntu7.9 +xz-utils==5.6.1+really5.4.5-1ubuntu0.2 +zip==3.0-13ubuntu0.2 +zlib1g==1:1.3.dfsg-3.1ubuntu2.1 +zlib1g-dev==1:1.3.dfsg-3.1ubuntu2.1 diff --git a/reports/vm-prebuilt-inventory-20260325/new_dpkg_status.txt b/reports/vm-prebuilt-inventory-20260325/new_dpkg_status.txt new file mode 100644 index 0000000000000000000000000000000000000000..b7fabee7468d6bde3d14a0d6af39f24a00544d70 GIT binary patch literal 769308 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

A!?R&p9c%vzMJ+lX@5tD@g4bStp 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 ZeO`8G{IO%;8A$B1EyyYAja$#c#7vD| Oxp+D?-C1t&ct3t)T@eKU literal 0 HcmV?d00001 diff --git a/reports/vm-prebuilt-inventory-20260325/npm-global.txt b/reports/vm-prebuilt-inventory-20260325/npm-global.txt new file mode 100644 index 00000000..2bad233c --- /dev/null +++ b/reports/vm-prebuilt-inventory-20260325/npm-global.txt @@ -0,0 +1,22 @@ +@mermaid-js +corepack +docx +graphviz +markdownlint-cli +markdownlint-cli2 +markdown-toc +marked +npm +pdfjs-dist +pdf-lib +playwright +pptxgenjs +react +react-dom +react-icons +remark-cli +remark-preset-lint-recommended +sharp +ts-node +tsx +typescript diff --git a/reports/vm-prebuilt-inventory-20260325/pip-dist-info.txt b/reports/vm-prebuilt-inventory-20260325/pip-dist-info.txt new file mode 100644 index 00000000..9d0b3642 --- /dev/null +++ b/reports/vm-prebuilt-inventory-20260325/pip-dist-info.txt @@ -0,0 +1,83 @@ +backrefs-6.2 +beautifulsoup4-4.14.3 +blinker-1.9.0 +camelot_py-1.0.9 +charset_normalizer-3.4.6 +click-8.3.1 +contourpy-1.3.3 +cycler-0.12.1 +defusedxml-0.7.1 +deprecated-1.3.1 +docopt-0.6.2 +et_xmlfile-2.0.0 +flask-3.1.3 +flatbuffers-25.12.19 +fonttools-4.62.1 +ghp_import-2.1.0 +grip-4.6.2 +imageio_ffmpeg-0.6.0 +imageio-2.37.3 +img2pdf-0.6.3 +itsdangerous-2.2.0 +joblib-1.5.3 +kiwisolver-1.5.0 +lazy_loader-0.5 +lxml-6.0.2 +magika-0.6.3 +markdown-3.10.2 +markdownify-1.2.2 +markitdown-0.1.5 +marko-2.2.2 +matplotlib-3.10.8 +mergedeep-1.3.4 +mistune-3.2.0 +mkdocs_get_deps-0.2.2 +mkdocs_material_extensions-1.3.1 +mkdocs_material-9.7.6 +mkdocs-1.6.1 +mpmath-1.3.0 +networkx-3.6.1 +numpy-2.4.3 +odfpy-1.4.1 +onnxruntime-1.24.4 +opencv_contrib_python-4.13.0.92 +opencv_python_headless-4.13.0.92 +opencv_python-4.13.0.92 +openpyxl-3.1.5 +paginate-0.5.7 +pandas-3.0.1 +path_and_address-2.0.1 +pathspec-1.0.4 +pdf2image-1.17.0 +pdfkit-1.0.0 +pdfminer_six-20251230 +pdfplumber-0.11.9 +pikepdf-10.5.1 +pillow-12.1.1 +protobuf-7.34.1 +pymdown_extensions-10.21 +pymupdf-1.27.2.2 +pypdf-5.9.0 +pypdfium2-5.6.0 +pytesseract-0.3.13 +python_dateutil-2.9.0.post0 +python_docx-1.2.0 +python_dotenv-1.2.2 +python_pptx-1.0.2 +pyyaml_env_tag-1.1 +reportlab-4.4.10 +scikit_image-0.26.0 +scikit_learn-1.8.0 +scipy-1.17.1 +seaborn-0.13.2 +soupsieve-2.8.3 +sympy-1.14.0 +tabula_py-2.10.0 +tabulate-0.10.0 +threadpoolctl-3.6.0 +tifffile-2026.3.3 +wand-0.7.0 +watchdog-6.0.0 +werkzeug-3.1.7 +wrapt-2.1.2 +xlsxwriter-3.2.9 diff --git a/reports/vm-prebuilt-inventory-20260325/pip-freeze-new.txt b/reports/vm-prebuilt-inventory-20260325/pip-freeze-new.txt new file mode 100644 index 0000000000000000000000000000000000000000..270b48a98e0ead5f965e165752f859afb0e08394 GIT binary patch literal 1246 zcmY*Y!A|2)5c3&nKLtdR7T5y^?jUjHgwk%H5upuf3N0TGYmeU}MbS&7%#7{v%=`Yj z#{mzx!5waK&F2DVIOnPG2Pdd8;2BG7@QNNC8jL*3E0}Y|Q$*f^e{1X*ZSll!hb?P% zteHjjEHd>r?vnXK%5Ed0Bx@P$C9_J*19yp4XxIkxDb_?CsHG-Di_bujnu;|-zerVz zF7Xn`QaN>`Ub;Z314q@0s(O~HH{=;Z|G*9ofuerJQl^{(@#V)5Q6UtJWj~%+EexTE zZyQ-wIAyi?$E-SH!Y-1VFI4}*BM&>g|FB5Ioaa0=bl9_lE~@j?tWN%nF|Q53@?vqs zA+1+|u;zp`S9fUNvxhw~eYoO+_S3VvtcLR7x$eV zQoK)?ib$2!JF-tzj<{nf73)C8Ns~FJ>H~XTk#~*LP^XRcX4)&U{Y{KL^R~>N@Q-Wd zUPGMsA1$iQD#i1eI>@^#cQU4L#z2HO@;AZbJrGJ2+u|+vcI&qM-j{jk^lWt-)%D~{ zfy?N^5#KgtG_f)ATs+!-Ph_NX3o7aO&9~TltG68(!(n^-NThvoK)7;r+T-GA6Xk>U zKn&)UAKFwi?>I5-g7&aV8QQZcE(dhigt3YYIq93L{&r~w)~lG^{a|*($CFjG3RfKc E4;t>Xh5!Hn literal 0 HcmV?d00001 diff --git a/reports/vm-prebuilt-inventory-20260325/rebuild-comparison.md b/reports/vm-prebuilt-inventory-20260325/rebuild-comparison.md new file mode 100644 index 00000000..41620028 --- /dev/null +++ b/reports/vm-prebuilt-inventory-20260325/rebuild-comparison.md @@ -0,0 +1,52 @@ +# openagent-prebuilt Rebuild Comparison (2026-03-25) + +## Summary + +- Old prebuilt tar (before clean rebuild): `6,693,079,040` bytes (`6.23 GB`) +- New prebuilt tar (rebuilt from clean `openagent-build` + current setup scripts): `2,019,061,760` bytes (`1.88 GB`) +- Delta: `-4,674,017,280` bytes (`-4.35 GB`, about `-69.8%`) + +## Hash + +- Old SHA256: `61279AF095540D3C1290BDF8B2BA1F4094BD128C347E873BEF0F9D25A56986D6` +- New SHA256: `8D8F7F8718891C8242DEE44409EA92EB57B912FCBA53F427622C2DE70B92A022` + +## Package Count Changes + +- APT packages: + - old: `964` + - new: `386` + - source files: + - `apt-installed.txt` + - `apt-installed-new.txt` +- Python packages: + - old: `83` (from old dist-info snapshot extraction) + - new: `35` (from `pip list --format=freeze` in rebuilt distro) + - source files: + - `pip-dist-info.txt` + - `pip-freeze-new.txt` +- NPM global packages: + - old: `22` + - new: `5` + - source files: + - `npm-global.txt` + - `npm-global-new.txt` + +## New Prebuilt Size Hotspots + +Top large files (`>50 MB`) in rebuilt tar: + +- `opt/pw-browsers/chromium-1208/chrome-linux64/chrome` (`257.28 MB`) +- `opt/pw-browsers/chromium_headless_shell-1208/chrome-headless-shell-linux64/chrome-headless-shell` (`175.05 MB`) +- `usr/bin/node` (`118.90 MB`) +- `usr/lib/x86_64-linux-gnu/libLLVM-15.so.1` (`111.46 MB`) +- `usr/local/bin/uv` (`56.33 MB`) + +## Notes + +- During the first rebuild attempt, `05_pip.sh` did not fail even when pip installs failed. +- Root causes fixed in source: + - `setup.sh`: `pip_install` now conditionally adds `--break-system-packages` only when supported. + - `05_pip.sh`: now uses `set -euo pipefail` to fail fast on pip errors. + - `03_apt.sh`: each `apt_install` call now hard-fails on error (`|| exit 1`). +- After fixes, rebuild completed and dependencies were actually installed. From 3f1a6f9cfa83d53165fc059787556b2551723657 Mon Sep 17 00:00:00 2001 From: zhanghr136 Date: Fri, 27 Mar 2026 09:38:31 +0800 Subject: [PATCH 13/28] feat: add uninstallation script to clean up user data --- libs/openagent_demo/electron/package.json | 4 +++- libs/openagent_demo/electron/resources/installer.nsh | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 libs/openagent_demo/electron/resources/installer.nsh diff --git a/libs/openagent_demo/electron/package.json b/libs/openagent_demo/electron/package.json index 6dbd8113..6f3e2699 100644 --- a/libs/openagent_demo/electron/package.json +++ b/libs/openagent_demo/electron/package.json @@ -77,7 +77,9 @@ }, "nsis": { "oneClick": false, - "allowToChangeInstallationDirectory": true + "allowToChangeInstallationDirectory": true, + "deleteAppDataOnUninstall": true, + "include": "resources/installer.nsh" } } } diff --git a/libs/openagent_demo/electron/resources/installer.nsh b/libs/openagent_demo/electron/resources/installer.nsh new file mode 100644 index 00000000..7060541b --- /dev/null +++ b/libs/openagent_demo/electron/resources/installer.nsh @@ -0,0 +1,3 @@ +!macro customUnInstall + RMDir /r "$PROFILE\.openagent" +!macroend From d16536e165fcfaa904b0ddd081cdd0b766ad9160 Mon Sep 17 00:00:00 2001 From: xuelin-cell Date: Thu, 26 Mar 2026 19:18:53 +0800 Subject: [PATCH 14/28] chore(repo): track wsl prebuilt tar with LFS and ignore dist zip --- .gitattributes | 1 + .gitignore | 3 +++ libs/openagent/sandbox/vm/wsl/prebuilt/openagent-prebuilt.tar | 3 +++ 3 files changed, 7 insertions(+) create mode 100644 libs/openagent/sandbox/vm/wsl/prebuilt/openagent-prebuilt.tar diff --git a/.gitattributes b/.gitattributes index 0793bb27..c88eb060 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ libs/openagent/sandbox/vm/setup/*.sh text eol=lf libs/openagent/sandbox/vm/setup/steps/*.sh text eol=lf +libs/openagent/sandbox/vm/wsl/prebuilt/openagent-prebuilt.tar filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index 02e7f45a..ff18f0b1 100644 --- a/.gitignore +++ b/.gitignore @@ -220,3 +220,6 @@ mounts/ # Git worktrees .trees/ + +# Electron build artifact +libs/openagent_demo/electron/dist.zip diff --git a/libs/openagent/sandbox/vm/wsl/prebuilt/openagent-prebuilt.tar b/libs/openagent/sandbox/vm/wsl/prebuilt/openagent-prebuilt.tar new file mode 100644 index 00000000..717a5a6d --- /dev/null +++ b/libs/openagent/sandbox/vm/wsl/prebuilt/openagent-prebuilt.tar @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb39247403141d4bd20516ce9fbb25d5956a11f838e577959a7bed4bc6514a79 +size 2019061760 From 5c0c87838a2e02e8b56a890c5bd2c49cf99fc518 Mon Sep 17 00:00:00 2001 From: xuelin-cell Date: Fri, 27 Mar 2026 14:15:50 +0800 Subject: [PATCH 15/28] fix(windows-vm): improve onboarding recovery and setup status --- .gitattributes | 2 + .../hexagent/computer/local/vm_win.py | 2 +- .../backend/hexagent_api/agent_manager.py | 4 +- .../backend/hexagent_api/routes/setup.py | 6 +- .../src/components/OnboardingWizard.tsx | 136 +++++++++++++++++- .../frontend/src/components/SettingsModal.tsx | 23 ++- libs/hexagent_demo/frontend/src/vmSetup.tsx | 12 ++ 7 files changed, 166 insertions(+), 19 deletions(-) diff --git a/.gitattributes b/.gitattributes index c88eb060..c3482579 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,5 @@ libs/openagent/sandbox/vm/setup/*.sh text eol=lf libs/openagent/sandbox/vm/setup/steps/*.sh text eol=lf +libs/hexagent/sandbox/vm/setup/*.sh text eol=lf +libs/hexagent/sandbox/vm/setup/steps/*.sh text eol=lf libs/openagent/sandbox/vm/wsl/prebuilt/openagent-prebuilt.tar filter=lfs diff=lfs merge=lfs -text diff --git a/libs/hexagent/hexagent/computer/local/vm_win.py b/libs/hexagent/hexagent/computer/local/vm_win.py index 9ea9bb37..58fc0acb 100644 --- a/libs/hexagent/hexagent/computer/local/vm_win.py +++ b/libs/hexagent/hexagent/computer/local/vm_win.py @@ -336,7 +336,7 @@ async def mount( for r in resolved_new: probe = await self._vm.shell(f"findmnt -n {shlex.quote(r.guest_path)}") if probe.exit_code != 0: - from openagent.computer.local._wsl import _session_user_from_guest_mount_path, _win_path_to_wsl + from hexagent.computer.local._wsl import _session_user_from_guest_mount_path, _win_path_to_wsl wsl_host = _win_path_to_wsl(r.host_path) qguest = shlex.quote(r.guest_path) diff --git a/libs/hexagent_demo/backend/hexagent_api/agent_manager.py b/libs/hexagent_demo/backend/hexagent_api/agent_manager.py index 50dc7d59..20a3164e 100644 --- a/libs/hexagent_demo/backend/hexagent_api/agent_manager.py +++ b/libs/hexagent_demo/backend/hexagent_api/agent_manager.py @@ -152,7 +152,7 @@ async def _ensure_computer( import shutil if sys.platform == "win32": - from openagent.computer.local._wsl import _resolve_wsl_exe + from hexagent.computer.local._wsl import _resolve_wsl_exe vm_backend_ready = _resolve_wsl_exe() is not None else: @@ -469,7 +469,7 @@ def _schedule_vm_warmup(self) -> None: import shutil if sys.platform == "win32": - from openagent.computer.local._wsl import _resolve_wsl_exe + from hexagent.computer.local._wsl import _resolve_wsl_exe vm_backend_available = _resolve_wsl_exe() is not None else: diff --git a/libs/hexagent_demo/backend/hexagent_api/routes/setup.py b/libs/hexagent_demo/backend/hexagent_api/routes/setup.py index 9f800fc9..fbddd5d4 100644 --- a/libs/hexagent_demo/backend/hexagent_api/routes/setup.py +++ b/libs/hexagent_demo/backend/hexagent_api/routes/setup.py @@ -1419,11 +1419,13 @@ async def check_markers(self) -> dict[str, object]: if sys.platform == "win32": instance_status = await _wsl_instance_status() shell = lambda cmd: _wsl_shell(cmd, user="root") + if not _wsl_distro_ready_for_cowork(instance_status): + return {"provisioned": False, "steps_done": [], "total_steps": len(_PROVISION_STEPS)} else: instance_status = await _lima_instance_status() shell = _lima_shell - if instance_status != "Running": - return {"provisioned": False, "steps_done": [], "total_steps": len(_PROVISION_STEPS)} + if instance_status != "Running": + return {"provisioned": False, "steps_done": [], "total_steps": len(_PROVISION_STEPS)} rc, stdout, _ = await shell(f"ls {_SETUP_MARKER_DIR}/*.done 2>/dev/null || true") if rc != 0 or not stdout.strip(): diff --git a/libs/hexagent_demo/frontend/src/components/OnboardingWizard.tsx b/libs/hexagent_demo/frontend/src/components/OnboardingWizard.tsx index 60e2fd6a..94298b13 100644 --- a/libs/hexagent_demo/frontend/src/components/OnboardingWizard.tsx +++ b/libs/hexagent_demo/frontend/src/components/OnboardingWizard.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useRef } from "react"; import faviconSvg from "../assets/favicon.svg"; import { Eye, EyeOff, ArrowRight, ChevronDown, ChevronRight, @@ -84,10 +84,52 @@ function stepIndex(s: Step): number { return STEPS.indexOf(s); } +const ONBOARDING_DRAFT_KEY = "hexagent-onboarding-draft-v1"; + +interface OnboardingDraft { + step?: Step; + selectedProviderId?: string; + apiKey?: string; + modelId?: string; + displayName?: string; + baseUrl?: string; + sumProviderId?: string; + sumApiKey?: string; + sumModelId?: string; + sumDisplayName?: string; + sumBaseUrl?: string; + sumSameAsMain?: boolean; + searchProvider?: string; + searchKey?: string; + fetchProvider?: string; + fetchKey?: string; + e2bKey?: string; + vmSkipped?: boolean; +} + +function loadOnboardingDraft(): OnboardingDraft | null { + try { + const raw = localStorage.getItem(ONBOARDING_DRAFT_KEY); + if (!raw) return null; + return JSON.parse(raw) as OnboardingDraft; + } catch { + return null; + } +} + +function saveOnboardingDraft(draft: OnboardingDraft): void { + localStorage.setItem(ONBOARDING_DRAFT_KEY, JSON.stringify(draft)); +} + +function clearOnboardingDraft(): void { + localStorage.removeItem(ONBOARDING_DRAFT_KEY); +} + // --------------------------------------------------------------------------- export default function OnboardingWizard({ open, onComplete, settings, onSettingsChange }: OnboardingWizardProps) { const { dispatch } = useAppContext(); + const draftReadyRef = useRef(false); const [step, setStep] = useState("welcome"); const [saving, setSaving] = useState(false); const [error, setError] = useState(""); @@ -126,6 +168,7 @@ export default function OnboardingWizard({ open, onComplete, settings, onSetting // VM setup — shared with Settings via VMSetupProvider (single source of truth) const vm = useVMSetup(); + const vmAutoBootstrapping = vm.autoBootstrapping; const [vmSkipped, setVmSkipped] = useState(false); const [showSkipConfirm, setShowSkipConfirm] = useState(false); const [showDepsPrompt, setShowDepsPrompt] = useState(false); @@ -141,14 +184,84 @@ export default function OnboardingWizard({ open, onComplete, settings, onSetting const vmUsable = vmPhase1 === "done" && vmPhase2 === "done"; const vmPhase1NeedsRestart = /restart windows|重启.*windows|重启.*电脑|reboot/i.test(vmPhase1Error || ""); - // Load server config and reset name on open + // Load server config and restore onboarding draft on open useEffect(() => { - if (open) { - getServerConfig().then(setConfig).catch(() => {}); - onSettingsChange((prev) => ({ ...prev, fullName: "" })); + if (!open) { + draftReadyRef.current = false; + return; } + + getServerConfig().then(setConfig).catch(() => {}); + + const draft = loadOnboardingDraft(); + if (draft) { + if (draft.step && STEPS.includes(draft.step)) setStep(draft.step); + if (draft.selectedProviderId) setSelectedProvider(PROVIDERS.find((p) => p.id === draft.selectedProviderId) ?? null); + if (typeof draft.apiKey === "string") setApiKey(draft.apiKey); + if (typeof draft.modelId === "string") setModelId(draft.modelId); + if (typeof draft.displayName === "string") setDisplayName(draft.displayName); + if (typeof draft.baseUrl === "string") setBaseUrl(draft.baseUrl); + if (draft.sumProviderId) setSumProvider(PROVIDERS.find((p) => p.id === draft.sumProviderId) ?? null); + if (typeof draft.sumApiKey === "string") setSumApiKey(draft.sumApiKey); + if (typeof draft.sumModelId === "string") setSumModelId(draft.sumModelId); + if (typeof draft.sumDisplayName === "string") setSumDisplayName(draft.sumDisplayName); + if (typeof draft.sumBaseUrl === "string") setSumBaseUrl(draft.sumBaseUrl); + if (typeof draft.sumSameAsMain === "boolean") setSumSameAsMain(draft.sumSameAsMain); + if (typeof draft.searchProvider === "string") setSearchProvider(draft.searchProvider); + if (typeof draft.searchKey === "string") setSearchKey(draft.searchKey); + if (typeof draft.fetchProvider === "string") setFetchProvider(draft.fetchProvider); + if (typeof draft.fetchKey === "string") setFetchKey(draft.fetchKey); + if (typeof draft.e2bKey === "string") setE2bKey(draft.e2bKey); + if (typeof draft.vmSkipped === "boolean") setVmSkipped(draft.vmSkipped); + } + + draftReadyRef.current = true; }, [open]); // eslint-disable-line react-hooks/exhaustive-deps + useEffect(() => { + if (!open || !draftReadyRef.current) return; + saveOnboardingDraft({ + step, + selectedProviderId: selectedProvider?.id, + apiKey, + modelId, + displayName, + baseUrl, + sumProviderId: sumProvider?.id, + sumApiKey, + sumModelId, + sumDisplayName, + sumBaseUrl, + sumSameAsMain, + searchProvider, + searchKey, + fetchProvider, + fetchKey, + e2bKey, + vmSkipped, + }); + }, [ + open, + step, + selectedProvider, + apiKey, + modelId, + displayName, + baseUrl, + sumProvider, + sumApiKey, + sumModelId, + sumDisplayName, + sumBaseUrl, + sumSameAsMain, + searchProvider, + searchKey, + fetchProvider, + fetchKey, + e2bKey, + vmSkipped, + ]); + if (!open) return null; // ── Navigation ── @@ -226,6 +339,7 @@ export default function OnboardingWizard({ open, onComplete, settings, onSetting const saved = await updateServerConfig(updated); dispatch({ type: "SET_SERVER_CONFIG", payload: saved }); + clearOnboardingDraft(); onComplete(); } catch (e: unknown) { setError(e instanceof Error ? e.message : "Failed to save configuration"); @@ -820,7 +934,11 @@ export default function OnboardingWizard({ open, onComplete, settings, onSetting {vmPhase1 === "done" && Installed} {vmPhase1 === "running" && vmPhase1Msg && {vmPhase1Msg}} {vmPhase1 === "pending" && ( - + vmAutoBootstrapping ? ( + Auto installing... + ) : ( + + ) )} {vmPhase1 === "error" && ( vmPhase1NeedsRestart ? ( @@ -848,7 +966,11 @@ export default function OnboardingWizard({ open, onComplete, settings, onSetting {vmPhase2 === "done" && Ready} {vmPhase2 === "running" && vmPhase2Msg && {vmPhase2Msg}} {vmPhase2 === "pending" && vmPhase1 === "done" && ( - + vmAutoBootstrapping ? ( + Auto installing... + ) : ( + + ) )} {vmPhase2 === "error" && ( diff --git a/libs/hexagent_demo/frontend/src/components/SettingsModal.tsx b/libs/hexagent_demo/frontend/src/components/SettingsModal.tsx index c2009b23..9ab9392e 100644 --- a/libs/hexagent_demo/frontend/src/components/SettingsModal.tsx +++ b/libs/hexagent_demo/frontend/src/components/SettingsModal.tsx @@ -1687,6 +1687,7 @@ function SandboxTab({ config, onConfigChange }: ConfigTabProps) { const { vmStatus, + autoBootstrapping, phase1, phase1Msg, phase1Error, @@ -1733,9 +1734,9 @@ function SandboxTab({ config, onConfigChange }: ConfigTabProps) { } }; - // Cowork mode should follow backend truth from /api/setup/vm (vm_ready). - // Keep phase fallback only when status payload is unavailable. - const vmUsable = vmStatus?.vm_ready ?? (phase1 === "done" && phase2 === "done"); + // Cowork mode should prefer backend truth from /api/setup/vm (vm_ready), + // but keep UI-consistent fallback when phase1+phase2 are already done. + const vmUsable = (vmStatus?.vm_ready === true) || (phase1 === "done" && phase2 === "done"); const allDone = vmUsable && phase3 === "done"; const anyRunning = phase1 === "running" || phase2 === "running" || phase3 === "running"; const coreError = phase1 === "error" || phase2 === "error"; @@ -1838,9 +1839,13 @@ function SandboxTab({ config, onConfigChange }: ConfigTabProps) { {phase1 === "done" && Installed} {phase1 === "running" && phase1Msg && {phase1Msg}} {phase1 === "pending" && ( - + autoBootstrapping ? ( + Auto installing... + ) : ( + + ) )} {phase1 === "error" && ( phase1NeedsRestart ? ( @@ -1890,7 +1895,11 @@ function SandboxTab({ config, onConfigChange }: ConfigTabProps) { {phase2 === "done" && Ready} {phase2 === "running" && phase2Msg && {phase2Msg}} {phase2 === "pending" && phase1 === "done" && ( - + autoBootstrapping ? ( + Auto installing... + ) : ( + + ) )} {phase2 === "error" && ( diff --git a/libs/hexagent_demo/frontend/src/vmSetup.tsx b/libs/hexagent_demo/frontend/src/vmSetup.tsx index 742bac12..ea3797c6 100644 --- a/libs/hexagent_demo/frontend/src/vmSetup.tsx +++ b/libs/hexagent_demo/frontend/src/vmSetup.tsx @@ -50,6 +50,7 @@ export type PhaseStatus = "checking" | "pending" | "running" | "done" | "error"; export interface VMSetupContextValue { vmStatus: VMStatus | null; + autoBootstrapping: boolean; phase1: PhaseStatus; phase1Msg: string; @@ -108,6 +109,7 @@ export function VMSetupProvider({ children }: { children: ReactNode }) { const [provStepMsg, setProvStepMsg] = useState>({}); const [provLog, setProvLog] = useState(null); const autoBootstrapTriggeredRef = useRef(false); + const [autoBootstrapping, setAutoBootstrapping] = useState(false); // SSE abort controllers (kept alive across renders, never aborted on unmount) const installCtrl = useRef(null); @@ -527,6 +529,7 @@ export function VMSetupProvider({ children }: { children: ReactNode }) { if (phase1 === "pending") { autoBootstrapTriggeredRef.current = true; + setAutoBootstrapping(true); notify("Detected first-time Windows setup. Starting VM runtime install automatically...", "info"); void doInstallLima(); return; @@ -534,13 +537,22 @@ export function VMSetupProvider({ children }: { children: ReactNode }) { if (phase1 === "done" && phase2 === "pending") { autoBootstrapTriggeredRef.current = true; + setAutoBootstrapping(true); notify("Runtime is ready. Starting VM instance setup automatically...", "info"); attachBuild(); } }, [vmStatus, phase1, phase2]); // eslint-disable-line react-hooks/exhaustive-deps + useEffect(() => { + if (!autoBootstrapping) return; + if (phase2 === "done" || phase1 === "error" || phase2 === "error") { + setAutoBootstrapping(false); + } + }, [autoBootstrapping, phase1, phase2]); + const value: VMSetupContextValue = { vmStatus, + autoBootstrapping, phase1, phase1Msg, phase1Error, phase2, phase2Msg, phase2Error, phase3, phase3Error, From 077da7dfba5328a5120da7ccc1b3be6ee85ea61c Mon Sep 17 00:00:00 2001 From: "Anqi (Anthony) Tang" Date: Fri, 27 Mar 2026 16:47:12 +0800 Subject: [PATCH 16/28] review(pr13): address admin feedback, add setup_lite, fix CI failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Admin feedback: - Revert harness/environment.py: restore ValueError on empty datetime; graceful fallback was rejected — failure must surface, not be hidden - Revert langchain/middleware.py: remove mid-conversation working-directory refresh; cwd is intentionally immutable after conversation start - Restore sandbox/vm/setup/ to pre-PR state; full package list must not be overwritten by this branch - Add sandbox/vm/setup_lite/ as a lite variant for demo/Electron deployments: trimmed APT/npm/pip baselines, bundled-browser detection, China-mirror support - Add vm_setup_lite_dir() to paths.py; point Lima and WSL provisioners in routes/setup.py at setup_lite/ (fix Lima tar to derive folder name dynamically instead of hardcoding "setup") - Fix .gitignore: correct stale openagent_demo reference, ignore .vite/ cache, WSL prebuilt tars, and reports/ directory CI fixes: - Fix ruff format: collapse multi-line f-string shell commands in vm_win.py; add missing blank line in test_vm.py - Fix ruff lint in _wsl.py: SYSTEMROOT capitalisation (SIM112), Path.cwd() over os.getcwd() (PTH109), noqa for assert (S101) and magic number (PLR2004); noqa PLR0912 on mount() in vm_win.py - Fix mypy method-assign errors in test_wsl.py - Fix test_mount_idempotent_self_heals_missing_live_mount: patch _win_path_to_wsl so the test runs on Linux CI where tmp_path is not a Windows drive-letter path --- .gitignore | 11 +- libs/hexagent/hexagent/computer/local/_wsl.py | 10 +- .../hexagent/computer/local/vm_win.py | 12 +- libs/hexagent/hexagent/harness/environment.py | 16 +- .../hexagent/hexagent/langchain/middleware.py | 23 - libs/hexagent/sandbox/vm/setup/setup.sh | 144 +-- .../hexagent/sandbox/vm/setup/steps/03_apt.sh | 68 +- .../hexagent/sandbox/vm/setup/steps/04_npm.sh | 34 +- .../hexagent/sandbox/vm/setup/steps/05_pip.sh | 64 +- .../sandbox/vm/setup/steps/06_playwright.sh | 49 +- libs/hexagent/sandbox/vm/setup_lite/setup.sh | 458 +++++++++ .../sandbox/vm/setup_lite/steps/01_base.sh | 9 + .../sandbox/vm/setup_lite/steps/02_nodejs.sh | 60 ++ .../sandbox/vm/setup_lite/steps/03_apt.sh | 41 + .../sandbox/vm/setup_lite/steps/04_npm.sh | 11 + .../sandbox/vm/setup_lite/steps/05_pip.sh | 18 + .../vm/setup_lite/steps/06_playwright.sh | 78 ++ .../vm/setup_lite/steps/07_finalize.sh | 10 + .../sandbox/vm/setup_lite/steps/08_cleanup.sh | 10 + .../unit_tests/computer/test_local_vm_win.py | 3 +- .../tests/unit_tests/computer/test_vm.py | 1 + .../tests/unit_tests/computer/test_wsl.py | 4 +- .../unit_tests/harness/test_environment.py | 2 - .../backend/hexagent_api/paths.py | 10 + .../backend/hexagent_api/routes/setup.py | 8 +- .../vm/wsl/prebuilt/openagent-prebuilt.tar | 3 - .../apt-installed-new.txt | Bin 3090 -> 0 bytes .../apt-installed.txt | 964 ------------------ .../new_dpkg_status.txt | Bin 769308 -> 0 bytes .../npm-global-new.txt | Bin 90 -> 0 bytes .../npm-global.txt | 22 - .../pip-dist-info.txt | 83 -- .../pip-freeze-new.txt | Bin 1246 -> 0 bytes .../rebuild-comparison.md | 52 - 34 files changed, 907 insertions(+), 1371 deletions(-) create mode 100755 libs/hexagent/sandbox/vm/setup_lite/setup.sh create mode 100755 libs/hexagent/sandbox/vm/setup_lite/steps/01_base.sh create mode 100755 libs/hexagent/sandbox/vm/setup_lite/steps/02_nodejs.sh create mode 100755 libs/hexagent/sandbox/vm/setup_lite/steps/03_apt.sh create mode 100755 libs/hexagent/sandbox/vm/setup_lite/steps/04_npm.sh create mode 100755 libs/hexagent/sandbox/vm/setup_lite/steps/05_pip.sh create mode 100755 libs/hexagent/sandbox/vm/setup_lite/steps/06_playwright.sh create mode 100755 libs/hexagent/sandbox/vm/setup_lite/steps/07_finalize.sh create mode 100755 libs/hexagent/sandbox/vm/setup_lite/steps/08_cleanup.sh delete mode 100644 libs/openagent/sandbox/vm/wsl/prebuilt/openagent-prebuilt.tar delete mode 100644 reports/vm-prebuilt-inventory-20260325/apt-installed-new.txt delete mode 100644 reports/vm-prebuilt-inventory-20260325/apt-installed.txt delete mode 100644 reports/vm-prebuilt-inventory-20260325/new_dpkg_status.txt delete mode 100644 reports/vm-prebuilt-inventory-20260325/npm-global-new.txt delete mode 100644 reports/vm-prebuilt-inventory-20260325/npm-global.txt delete mode 100644 reports/vm-prebuilt-inventory-20260325/pip-dist-info.txt delete mode 100644 reports/vm-prebuilt-inventory-20260325/pip-freeze-new.txt delete mode 100644 reports/vm-prebuilt-inventory-20260325/rebuild-comparison.md diff --git a/.gitignore b/.gitignore index 6aa7f618..6c9f2a64 100644 --- a/.gitignore +++ b/.gitignore @@ -222,4 +222,13 @@ mounts/ .trees/ # Electron build artifact -libs/openagent_demo/electron/dist.zip +libs/hexagent_demo/electron/dist.zip + +# Vite dev-server cache +**/.vite/ + +# WSL prebuilt VM image (build artifact — generate with prepare-wsl-prebuilt.ps1) +**/wsl/prebuilt/*.tar + +# One-off investigation/diagnostic reports +reports/ diff --git a/libs/hexagent/hexagent/computer/local/_wsl.py b/libs/hexagent/hexagent/computer/local/_wsl.py index 214a870a..7a6aa956 100644 --- a/libs/hexagent/hexagent/computer/local/_wsl.py +++ b/libs/hexagent/hexagent/computer/local/_wsl.py @@ -54,7 +54,7 @@ def _resolve_wsl_exe() -> str | None: w = shutil.which("wsl.exe") or shutil.which("wsl") if w: return w - system_root = os.environ.get("SystemRoot") or os.environ.get("WINDIR") + system_root = os.environ.get("SYSTEMROOT") or os.environ.get("WINDIR") if not system_root: system_root = r"C:\Windows" candidate = Path(system_root) / "System32" / "wsl.exe" @@ -72,13 +72,13 @@ def _stable_host_cwd() -> str: unexpected context. Force a stable host cwd to avoid inheriting stale per-session paths. """ - system_root = os.environ.get("SystemRoot") or os.environ.get("WINDIR") or r"C:\Windows" + system_root = os.environ.get("SYSTEMROOT") or os.environ.get("WINDIR") or r"C:\Windows" # ``wsl.exe`` exists under System32 on supported hosts; use that directory # as a stable cwd if available, otherwise fall back to the process cwd. safe_dir = Path(system_root) / "System32" if safe_dir.is_dir(): return str(safe_dir) - return os.getcwd() + return str(Path.cwd()) def _ensure_proactor_event_loop() -> None: @@ -136,7 +136,7 @@ class WslVM: def __init__(self, instance: str) -> None: _check_wsl_prerequisites() wsl_exe = _resolve_wsl_exe() - assert wsl_exe is not None + assert wsl_exe is not None # noqa: S101 self._wsl_exe = wsl_exe self._instance = instance self._unc_prefix: str | None = None # cached after first successful probe @@ -698,7 +698,7 @@ def _session_user_from_guest_mount_path(guest_path: str) -> str | None: after ``sessions`` matches the Linux account created for that sandbox session. """ parts = guest_path.split("/") - if len(parts) >= 3 and parts[1] == "sessions" and parts[2]: + if len(parts) >= 3 and parts[1] == "sessions" and parts[2]: # noqa: PLR2004 return parts[2] return None diff --git a/libs/hexagent/hexagent/computer/local/vm_win.py b/libs/hexagent/hexagent/computer/local/vm_win.py index 58fc0acb..890433a2 100644 --- a/libs/hexagent/hexagent/computer/local/vm_win.py +++ b/libs/hexagent/hexagent/computer/local/vm_win.py @@ -281,7 +281,7 @@ async def stop(self) -> None: return await self._vm.stop() - async def mount( + async def mount( # noqa: PLR0912 self, mounts: Mount | list[Mount], *, @@ -605,19 +605,13 @@ async def _create_user(self, name: str) -> None: sudo_probe = await self._vm.shell("command -v sudo >/dev/null 2>&1") sudo_prefix = "sudo " if sudo_probe.exit_code == 0 else "" - create_cmd = ( - f"{sudo_prefix}useradd -m -d {qhome} -s /bin/bash " - f"--no-log-init -K SUB_UID_COUNT=0 -K SUB_GID_COUNT=0 {qname}" - ) + create_cmd = f"{sudo_prefix}useradd -m -d {qhome} -s /bin/bash --no-log-init -K SUB_UID_COUNT=0 -K SUB_GID_COUNT=0 {qname}" result = await self._vm.shell(create_cmd) if result.exit_code != 0: # Some Ubuntu/WSL images reject useradd with SUB_UID/GID_COUNT=0. # Retry without those overrides for compatibility. We retry # regardless of locale-specific stderr text. - fallback_cmd = ( - f"{sudo_prefix}useradd -m -d {qhome} -s /bin/bash " - f"--no-log-init {qname}" - ) + fallback_cmd = f"{sudo_prefix}useradd -m -d {qhome} -s /bin/bash --no-log-init {qname}" result = await self._vm.shell(fallback_cmd) if result.exit_code != 0: msg = f"Failed to create session user '{name}': {result.stderr}" diff --git a/libs/hexagent/hexagent/harness/environment.py b/libs/hexagent/hexagent/harness/environment.py index e6861289..27ff1efb 100644 --- a/libs/hexagent/hexagent/harness/environment.py +++ b/libs/hexagent/hexagent/harness/environment.py @@ -72,15 +72,13 @@ async def resolve(self) -> EnvironmentContext: # Parse into a timezone-aware datetime. # Shell outputs ISO 8601 with numeric offset, e.g. "2026-02-14T10:30:00-0800". raw_dt = values[5] - if raw_dt: - try: - now = datetime.strptime(raw_dt, "%Y-%m-%dT%H:%M:%S%z") - except ValueError: - now = datetime.strptime(raw_dt[:19], "%Y-%m-%dT%H:%M:%S") # noqa: DTZ007 - else: - # Some hosts occasionally return empty stdout for this probe. - # Degrade gracefully so environment detection does not block task execution. - now = datetime.now().astimezone() + if not raw_dt: + msg = f"Environment shell returned empty datetime (raw output: {result.stdout!r})" + raise ValueError(msg) + try: + now = datetime.strptime(raw_dt, "%Y-%m-%dT%H:%M:%S%z") + except ValueError: + now = datetime.strptime(raw_dt[:19], "%Y-%m-%dT%H:%M:%S") # noqa: DTZ007 return EnvironmentContext( working_dir=values[0], diff --git a/libs/hexagent/hexagent/langchain/middleware.py b/libs/hexagent/hexagent/langchain/middleware.py index 32a504b5..fb8f66ac 100644 --- a/libs/hexagent/hexagent/langchain/middleware.py +++ b/libs/hexagent/hexagent/langchain/middleware.py @@ -381,26 +381,6 @@ async def abefore_model( Group 3: Annotators (system reminders). """ messages: list[BaseMessage] = list(state["messages"]) - env_prompt_updated = False - - # Workspace path can change after agent creation (e.g. cowork warm-up - # creates the agent, then PATCH mounts the user's folder and sets - # default_cwd). Tools see the new cwd, but the system prompt still - # lists the old ``pwd`` from EnvironmentResolver — follow-up turns may - # run size/list commands against the wrong directory. Refresh when pwd - # drifts (same pattern as compaction rebuild). - if self._environment_resolver is not None and self._prompt_profile is not None: - fresh_env = await self._environment_resolver.resolve() - old_wd = self._context.environment.working_dir if self._context.environment else None - if fresh_env.working_dir != old_wd: - self._context = replace(self._context, environment=fresh_env) - new_content = compose(self._prompt_profile, self._context) - if self._custom_prompt: - new_content = f"{self._custom_prompt}\n\n{new_content}" - if messages and isinstance(messages[0], SystemMessage): - messages[0] = SystemMessage(content=new_content) - self._system_prompt = new_content - env_prompt_updated = True # --- GROUP 1: Intercepts (compaction phases) --- phase = CompactionPhase(state.get("compaction_phase", CompactionPhase.NONE)) @@ -479,9 +459,6 @@ async def abefore_model( if images_extracted: return {"messages": LangGraphOverwrite(messages)} - if env_prompt_updated: - return {"messages": LangGraphOverwrite(messages)} - return None @hook_config(can_jump_to=["model"]) diff --git a/libs/hexagent/sandbox/vm/setup/setup.sh b/libs/hexagent/sandbox/vm/setup/setup.sh index e1cc2179..ce3187e3 100755 --- a/libs/hexagent/sandbox/vm/setup/setup.sh +++ b/libs/hexagent/sandbox/vm/setup/setup.sh @@ -1,6 +1,6 @@ #!/bin/bash # ============================================================================= -# HexAgent VM Setup - Orchestrator +# HexAgent VM Setup — Orchestrator # ============================================================================= # Discovers and runs step scripts in order with progress reporting, # resumability (marker files), concurrency protection (flock), and @@ -21,7 +21,7 @@ set -uo pipefail # No -e: we handle errors per-step. -# ------ Constants ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +# ── Constants ──────────────────────────────────────────────────────────────── SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" STEPS_DIR="${SCRIPT_DIR}/steps" MARKER_DIR="/var/lib/hexagent/setup" @@ -29,31 +29,18 @@ LOG_DIR="/var/log/hexagent/setup" LOCK_FILE="/var/run/hexagent-setup.lock" LOCK_FD=9 -# ------ Environment (inherited by steps) ------------------------------------------------------------------------------------------------------------------------ +# ── Environment (inherited by steps) ──────────────────────────────────────── export DEBIAN_FRONTEND=noninteractive export PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsers export MARKER_DIR LOG_DIR -if grep -qi microsoft /proc/version 2>/dev/null; then - _OPENAGENT_DEFAULT_CN_MIRRORS=1 -else - _OPENAGENT_DEFAULT_CN_MIRRORS=0 -fi -OPENAGENT_USE_CN_MIRRORS="${OPENAGENT_USE_CN_MIRRORS:-${_OPENAGENT_DEFAULT_CN_MIRRORS}}" -OPENAGENT_APT_MIRROR="${OPENAGENT_APT_MIRROR:-https://mirrors.ustc.edu.cn/ubuntu}" -OPENAGENT_APT_PORTS_MIRROR="${OPENAGENT_APT_PORTS_MIRROR:-https://mirrors.ustc.edu.cn/ubuntu-ports}" -OPENAGENT_PIP_INDEX_URL="${OPENAGENT_PIP_INDEX_URL:-https://pypi.tuna.tsinghua.edu.cn/simple}" -OPENAGENT_NPM_REGISTRY="${OPENAGENT_NPM_REGISTRY:-https://registry.npmmirror.com}" -OPENAGENT_PLAYWRIGHT_DOWNLOAD_HOST="${OPENAGENT_PLAYWRIGHT_DOWNLOAD_HOST:-https://npmmirror.com/mirrors/playwright}" -export OPENAGENT_USE_CN_MIRRORS OPENAGENT_APT_MIRROR OPENAGENT_APT_PORTS_MIRROR -export OPENAGENT_PIP_INDEX_URL OPENAGENT_NPM_REGISTRY OPENAGENT_PLAYWRIGHT_DOWNLOAD_HOST - -# ------ CLI defaults --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +# ── CLI defaults ───────────────────────────────────────────────────────────── FORCE=false SINGLE_STEP="" LIST_ONLY=false RESET=false -# ------ CLI parsing ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +# ── CLI parsing ────────────────────────────────────────────────────────────── usage() { echo "Usage: sudo bash setup.sh [OPTIONS]" echo "" @@ -76,16 +63,16 @@ while [[ $# -gt 0 ]]; do esac done -# ------ Root check --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +# ── Root check ─────────────────────────────────────────────────────────────── if [[ "$(id -u)" -ne 0 ]]; then echo "ERROR: Must run as root (sudo)." >&2 exit 1 fi -# ------ Directory setup ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +# ── Directory setup ────────────────────────────────────────────────────────── mkdir -p "$MARKER_DIR" "$LOG_DIR" -# ------ emit() -progress protocol ------------------------------------------------------------------------------------------------------------------------------------------ +# ── emit() — progress protocol ────────────────────────────────────────────── # Writes to fd 3 which points to the original stdout (what the backend reads). # All other output (package managers) goes to the log file. emit() { @@ -95,7 +82,7 @@ emit() { } export -f emit -# ------ apt_install -retry wrapper ------------------------------------------------------------------------------------------------------------------------------------------ +# ── apt_install — retry wrapper ────────────────────────────────────────────── apt_install() { local max_attempts=5 local delay=3 @@ -144,34 +131,22 @@ apt_install() { } export -f apt_install -# ------ pip_install -retry wrapper ------------------------------------------------------------------------------------------------------------------------------------------ +# ── pip_install — retry wrapper ────────────────────────────────────────────── pip_install() { local max_attempts=5 local delay=5 local attempt=1 - local use_cn_mirrors="${OPENAGENT_USE_CN_MIRRORS:-0}" local pip_opts=( + --break-system-packages --timeout 120 --retries 3 - --no-cache-dir ) - if pip3 help install 2>/dev/null | grep -q -- "--break-system-packages"; then - pip_opts+=(--break-system-packages) - fi while [[ $attempt -le $max_attempts ]]; do echo ">>> pip install attempt $attempt/$max_attempts (${#} packages)" if pip3 install "${pip_opts[@]}" "$@"; then return 0 fi - if [[ "$use_cn_mirrors" == "1" ]]; then - echo ">>> Mirror install failed, retrying with official PyPI..." - if PIP_INDEX_URL="https://pypi.org/simple" \ - PIP_EXTRA_INDEX_URL="" \ - pip3 install "${pip_opts[@]}" "$@"; then - return 0 - fi - fi echo ">>> Attempt $attempt failed. Retrying in ${delay}s..." sleep $delay delay=$((delay * 2)) @@ -189,14 +164,6 @@ pip_install() { pkg_ok=true break fi - if [[ "$use_cn_mirrors" == "1" ]]; then - if PIP_INDEX_URL="https://pypi.org/simple" \ - PIP_EXTRA_INDEX_URL="" \ - pip3 install "${pip_opts[@]}" "$pkg" 2>&1; then - pkg_ok=true - break - fi - fi echo ">>> Failed: $pkg (attempt $pkg_attempt/3)" sleep $((pkg_attempt * 3)) pkg_attempt=$((pkg_attempt + 1)) @@ -215,61 +182,11 @@ pip_install() { } export -f pip_install -configure_cn_mirrors() { - if [[ "${OPENAGENT_USE_CN_MIRRORS}" != "1" ]]; then - return 0 - fi - - emit _meta progress "Applying China mirrors (APT/PIP/NPM/Playwright)" - - # sed replacement escapes for arbitrary mirror strings (e.g. containing '&' or '|') - local apt_mirror_esc apt_ports_mirror_esc - apt_mirror_esc="${OPENAGENT_APT_MIRROR//\\/\\\\}" - apt_mirror_esc="${apt_mirror_esc//&/\\&}" - apt_mirror_esc="${apt_mirror_esc//|/\\|}" - apt_ports_mirror_esc="${OPENAGENT_APT_PORTS_MIRROR//\\/\\\\}" - apt_ports_mirror_esc="${apt_ports_mirror_esc//&/\\&}" - apt_ports_mirror_esc="${apt_ports_mirror_esc//|/\\|}" - - if [[ -f /etc/apt/sources.list ]]; then - cp -n /etc/apt/sources.list /etc/apt/sources.list.openagent.bak 2>/dev/null || true - sed -Ei \ - -e "s|https?://(archive|security)\.ubuntu\.com/ubuntu|${apt_mirror_esc}|g" \ - -e "s|https?://ports\.ubuntu\.com/ubuntu-ports|${apt_ports_mirror_esc}|g" \ - /etc/apt/sources.list 2>/dev/null || true - fi - - if [[ -f /etc/apt/sources.list.d/ubuntu.sources ]]; then - cp -n /etc/apt/sources.list.d/ubuntu.sources /etc/apt/sources.list.d/ubuntu.sources.openagent.bak 2>/dev/null || true - sed -Ei \ - -e "s|^URIs:[[:space:]]*https?://(archive|security)\.ubuntu\.com/ubuntu/?$|URIs: ${apt_mirror_esc}|g" \ - -e "s|^URIs:[[:space:]]*https?://ports\.ubuntu\.com/ubuntu-ports/?$|URIs: ${apt_ports_mirror_esc}|g" \ - /etc/apt/sources.list.d/ubuntu.sources 2>/dev/null || true - fi - - cat >/etc/pip.conf </etc/profile.d/openagent-mirrors.sh < "${MARKER_DIR}/$1.done"; } -# ------ Step discovery --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +# ── Step discovery ─────────────────────────────────────────────────────────── discover_steps() { for f in "${STEPS_DIR}"/*.sh; do [[ -f "$f" ]] || continue @@ -282,41 +199,41 @@ step_desc() { sed -n '2s/^# *//p' "$1" } -# ------ --reset ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +# ── --reset ────────────────────────────────────────────────────────────────── if [[ "$RESET" == true ]]; then rm -f "${MARKER_DIR}"/*.done echo "All markers cleared." exit 0 fi -# ------ --list --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +# ── --list ─────────────────────────────────────────────────────────────────── if [[ "$LIST_ONLY" == true ]]; then while IFS= read -r step_file; do step_id="$(basename "$step_file" .sh)" if step_done "$step_id"; then echo "[done] $step_id ($(cat "${MARKER_DIR}/${step_id}.done"))" else - echo "[pending] $step_id -$(step_desc "$step_file")" + echo "[pending] $step_id — $(step_desc "$step_file")" fi done < <(discover_steps) exit 0 fi -# ------ Concurrency lock --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +# ── Concurrency lock ───────────────────────────────────────────────────────── exec 9>"$LOCK_FILE" if ! flock -n $LOCK_FD; then echo "ERROR: Another setup instance is running (lockfile: $LOCK_FILE)" >&2 exit 1 fi -# ------ fd redirection --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -# fd 3 = original stdout -backend reads @@SETUP: lines from here -# stdout + stderr -log file (all package manager noise) +# ── fd redirection ─────────────────────────────────────────────────────────── +# fd 3 = original stdout → backend reads @@SETUP: lines from here +# stdout + stderr → log file (all package manager noise) LOGFILE="${LOG_DIR}/setup-$(date +%Y%m%d-%H%M%S).log" exec 3>&1 exec 1>>"$LOGFILE" 2>&1 -# ------ Signal handling ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +# ── Signal handling ────────────────────────────────────────────────────────── HEARTBEAT_PID="" cleanup() { @@ -332,7 +249,7 @@ cancelled() { } trap cancelled SIGTERM SIGINT -# ------ Heartbeat ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +# ── Heartbeat ──────────────────────────────────────────────────────────────── start_heartbeat() { local step_id="$1" ( @@ -352,7 +269,7 @@ stop_heartbeat() { fi } -# ------ Preflight ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +# ── Preflight ──────────────────────────────────────────────────────────────── preflight() { emit _meta start "Preflight checks" @@ -364,9 +281,7 @@ preflight() { esac export ARCH - configure_cn_mirrors - - # Disk space (require >= 10 GB free on /) + # Disk space (require ≥10 GB free on /) local free_kb free_kb=$(df / --output=avail | tail -1 | tr -d ' ') if (( free_kb < 10485760 )); then @@ -377,7 +292,7 @@ preflight() { emit _meta done "Preflight OK (arch=$ARCH, free=$((free_kb / 1024))MB)" } -# ------ run_step --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +# ── run_step ───────────────────────────────────────────────────────────────── run_step() { local step_file="$1" local step_id @@ -410,12 +325,12 @@ run_step() { mark_done "$step_id" emit "$step_id" done "Completed in ${elapsed}s" else - emit "$step_id" error "Failed (exit $rc) after ${elapsed}s - see $LOGFILE" + emit "$step_id" error "Failed (exit $rc) after ${elapsed}s — see $LOGFILE" return $rc fi } -# ------ Main --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +# ── Main ───────────────────────────────────────────────────────────────────── main() { preflight @@ -447,7 +362,7 @@ main() { done < <(discover_steps) if [[ $failed -gt 0 ]]; then - emit _meta error "Setup failed - re-run to resume from failed step" + emit _meta error "Setup failed — re-run to resume from failed step" exit 1 fi @@ -455,4 +370,3 @@ main() { } main - diff --git a/libs/hexagent/sandbox/vm/setup/steps/03_apt.sh b/libs/hexagent/sandbox/vm/setup/steps/03_apt.sh index 82bec2e1..8e1929a0 100755 --- a/libs/hexagent/sandbox/vm/setup/steps/03_apt.sh +++ b/libs/hexagent/sandbox/vm/setup/steps/03_apt.sh @@ -1,31 +1,71 @@ #!/bin/bash -# System packages (minimal baseline; install extras on demand) +# System packages (core utils, Python, Java, PDF, LaTeX, fonts, etc.) set -uo pipefail # No -e: apt_install handles its own errors with retries. apt-get update -emit 03_apt progress "group=core_utils" +emit 03_apt progress "group=core_utils (17 packages)" apt_install \ - bash coreutils wget curl git zip unzip jq tree ripgrep \ - file findutils patch sqlite3 || exit 1 + bash coreutils wget curl git zip unzip bzip2 xz-utils \ + file findutils patch perl jq tree sqlite3 ripgrep \ + netcat-openbsd apt-transport-https software-properties-common -emit 03_apt progress "group=build_tools" -apt_install build-essential pkg-config || exit 1 +emit 03_apt progress "group=build_tools (2 packages)" +apt_install build-essential pkg-config -emit 03_apt progress "group=python" -apt_install python3 python3-dev python3-pip python3-venv pipx || exit 1 +emit 03_apt progress "group=python (5 packages)" +apt_install python3 python3-dev python3-pip python3-venv pipx -emit 03_apt progress "group=media" -apt_install imagemagick graphviz || exit 1 +emit 03_apt progress "group=java (1 package)" +apt_install default-jre-headless -emit 03_apt progress "group=fonts" +emit 03_apt progress "group=pdf_tools (5 packages)" +apt_install poppler-utils qpdf pdftk-java wkhtmltopdf ghostscript + +emit 03_apt progress "group=pandoc (1 package)" +apt_install pandoc + +emit 03_apt progress "group=libreoffice (5 packages)" +apt_install \ + libreoffice-writer libreoffice-calc libreoffice-impress \ + libreoffice-common libreoffice-java-common + +emit 03_apt progress "group=media (3 packages)" +apt_install imagemagick graphviz ffmpeg + +emit 03_apt progress "group=ocr (2 packages)" +apt_install tesseract-ocr tesseract-ocr-eng + +emit 03_apt progress "group=latex (9 packages)" +apt_install \ + texlive-base texlive-latex-base texlive-latex-recommended \ + texlive-latex-extra texlive-fonts-recommended texlive-xetex \ + texlive-science texlive-pictures latexmk + +emit 03_apt progress "group=fonts (12 packages)" +apt_install \ + fonts-liberation2 fonts-dejavu fonts-freefont-ttf \ + fonts-noto-cjk fonts-noto-color-emoji \ + fonts-crosextra-caladea fonts-crosextra-carlito \ + fonts-lmodern fonts-texgyre fonts-opensymbol \ + fonts-wqy-zenhei fonts-ipafont-gothic + +emit 03_apt progress "group=x11_display (5 packages)" +apt_install \ + xvfb x11-xkb-utils xfonts-scalable xfonts-cyrillic xfonts-utils + +emit 03_apt progress "group=browser_libs (17 packages)" apt_install \ - fonts-liberation2 fonts-dejavu || exit 1 + libnss3 libnss3-tools libatk1.0-0t64 libatk-bridge2.0-0t64 \ + libcups2t64 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \ + libxfixes3 libxrandr2 libgbm1 libasound2t64 libpango-1.0-0 \ + libcairo2 libatspi2.0-0t64 libgtk-3-0t64 libgtk-4-1 -emit 03_apt progress "group=dev_libs" +emit 03_apt progress "group=dev_libs (7 packages)" apt_install \ - libffi-dev zlib1g-dev libpng-dev libfreetype-dev libbz2-dev || exit 1 + libffi-dev zlib1g-dev libpng-dev libfreetype-dev libcairo2-dev \ + libglib2.0-dev libbz2-dev # Cleanup emit 03_apt progress "Cleaning apt cache" diff --git a/libs/hexagent/sandbox/vm/setup/steps/04_npm.sh b/libs/hexagent/sandbox/vm/setup/steps/04_npm.sh index 014e24f0..2d6fd675 100755 --- a/libs/hexagent/sandbox/vm/setup/steps/04_npm.sh +++ b/libs/hexagent/sandbox/vm/setup/steps/04_npm.sh @@ -1,11 +1,31 @@ #!/bin/bash -# NPM global packages (minimal baseline; install extras on demand) +# NPM global packages set -euo pipefail -if [[ "${OPENAGENT_USE_CN_MIRRORS:-0}" == "1" ]]; then - emit 04_npm progress "Configuring npm registry mirror" - npm config set registry "${OPENAGENT_NPM_REGISTRY}" >/dev/null 2>&1 || true -fi - emit 04_npm progress "Installing npm global packages" -npm install -g typescript tsx playwright +npm install -g \ + docx@9 \ + pptxgenjs@4.0.1 \ + pdf-lib@1.17.1 \ + pdfjs-dist \ + marked \ + markdown-toc \ + markdownlint-cli \ + markdownlint-cli2 \ + remark-cli \ + remark-preset-lint-recommended \ + @mermaid-js/mermaid-cli \ + graphviz \ + react \ + react-dom \ + react-icons \ + typescript \ + ts-node \ + tsx \ + sharp \ + playwright + +if [[ "$ARCH" == "x86_64" ]]; then + emit 04_npm progress "Installing markdown-pdf (x86_64 only)" + npm install -g markdown-pdf +fi diff --git a/libs/hexagent/sandbox/vm/setup/steps/05_pip.sh b/libs/hexagent/sandbox/vm/setup/steps/05_pip.sh index 1960556a..e0236d87 100755 --- a/libs/hexagent/sandbox/vm/setup/steps/05_pip.sh +++ b/libs/hexagent/sandbox/vm/setup/steps/05_pip.sh @@ -1,16 +1,64 @@ #!/bin/bash -# Python packages (minimal baseline; install extras on demand) -set -euo pipefail +# Python packages (11 batches) +set -uo pipefail +# No -e: pip_install handles its own retries. -emit 05_pip progress "Core data & visualization" -pip_install numpy pandas matplotlib pillow +# Preflight: fix blinker conflict with system Flask +emit 05_pip progress "Preflight: fixing blinker" +pip3 install --break-system-packages --timeout 120 --ignore-installed blinker -emit 05_pip progress "Web & HTTP" -pip_install requests beautifulsoup4 lxml +emit 05_pip progress "Batch 1/11 — Core numeric (4 packages)" +pip_install numpy pandas scipy sympy -emit 05_pip progress "Utilities" +emit 05_pip progress "Batch 2/11 — ML / CV (3 packages)" +pip_install scikit-learn scikit-image onnxruntime + +emit 05_pip progress "Batch 3/11 — ML / CV OpenCV (3 packages)" +pip_install opencv-python opencv-contrib-python opencv-python-headless + +emit 05_pip progress "Batch 4/11 — Visualization (3 packages)" +pip_install matplotlib seaborn networkx + +emit 05_pip progress "Batch 5/11 — Image / media (5 packages)" +pip_install pillow imageio imageio-ffmpeg Wand pytesseract + +emit 05_pip progress "Batch 6/11 — PDF tools (11 packages)" +pip_install \ + pdfplumber pdfminer.six pypdf pikepdf pdf2image pdfkit \ + img2pdf camelot-py tabula-py reportlab pypdfium2 pymupdf + +emit 05_pip progress "Batch 7/11 — Office documents (5 packages)" +pip_install python-docx python-pptx openpyxl xlsxwriter odfpy + +emit 05_pip progress "Batch 8/11 — Markdown / docs (11 packages)" pip_install \ - uv click pyyaml python-dotenv tabulate + markitdown markdownify markdown grip mistune markdown-it-py \ + marko mkdocs mkdocs-material mkdocs-material-extensions \ + mkdocs-get-deps pymdown-extensions + +emit 05_pip progress "Batch 9/11 — Web / HTTP (5 packages)" +pip_install requests beautifulsoup4 lxml Flask httplib2 + +emit 05_pip progress "Batch 10/11 — Automation / browser (3 packages)" +pip_install playwright unoserver pyoo + +emit 05_pip progress "Batch 11/11 — System utilities (14 packages)" +pip_install \ + uv magika click colorama coloredlogs humanfriendly tabulate \ + python-dotenv psutil watchdog sounddevice pycairo graphviz freetype-py + +# Foundational (many already installed as transitive deps — pip will no-op) +emit 05_pip progress "Foundational / low-level (17 packages)" +pip_install \ + attrs bcrypt jsonschema python-magic livereload tornado PyYAML \ + certifi charset-normalizer cryptography defusedxml idna joblib \ + packaging protobuf python-dateutil pytz typing_extensions urllib3 + +# Platform-specific +if [[ "$ARCH" == "x86_64" ]]; then + emit 05_pip progress "Platform-specific: mediapipe (x86_64 only)" + pip_install "mediapipe>=0.10.32" +fi # Cleanup emit 05_pip progress "Cleaning pip cache" diff --git a/libs/hexagent/sandbox/vm/setup/steps/06_playwright.sh b/libs/hexagent/sandbox/vm/setup/steps/06_playwright.sh index c1b344b8..f017ffbf 100755 --- a/libs/hexagent/sandbox/vm/setup/steps/06_playwright.sh +++ b/libs/hexagent/sandbox/vm/setup/steps/06_playwright.sh @@ -6,13 +6,11 @@ set -euo pipefail dpkg --configure -a || true apt-get install -y -f || true -# Install Playwright OS deps (apt) retry-wrapped +# Install Playwright OS deps (apt) — retry-wrapped max_attempts=5 -deps_ok=0 for ((attempt = 1; attempt <= max_attempts; attempt++)); do emit 06_playwright progress "install-deps attempt $attempt/$max_attempts" if npx playwright install-deps chromium; then - deps_ok=1 break fi echo ">>> Retrying in 5s..." @@ -22,52 +20,9 @@ for ((attempt = 1; attempt <= max_attempts; attempt++)); do apt-get update || true done -if [[ $deps_ok -ne 1 ]]; then - emit 06_playwright error "Failed to install Playwright system dependencies" - exit 1 -fi - # Download Chromium binary emit 06_playwright progress "Downloading Chromium binary" -mirror_host="${OPENAGENT_PLAYWRIGHT_DOWNLOAD_HOST:-}" -browser_ok=0 - -# If bundled browsers are already present in the VM image, skip network download. -if find /opt/pw-browsers -type f \( -name "chrome-headless-shell" -o -name "chrome" \) 2>/dev/null | grep -q .; then - emit 06_playwright progress "Bundled Playwright browser detected under /opt/pw-browsers, skipping download" - browser_ok=1 -fi - -for ((attempt = 1; attempt <= max_attempts; attempt++)); do - if [[ $browser_ok -eq 1 ]]; then - break - fi - - if [[ "${OPENAGENT_USE_CN_MIRRORS:-0}" == "1" && -n "$mirror_host" ]]; then - emit 06_playwright progress "browser install attempt $attempt/$max_attempts (mirror)" - if PLAYWRIGHT_DOWNLOAD_HOST="$mirror_host" \ - PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsers \ - npx playwright install chromium; then - browser_ok=1 - break - fi - emit 06_playwright progress "Mirror unavailable, retrying with official host" - else - emit 06_playwright progress "browser install attempt $attempt/$max_attempts" - fi - - if env -u PLAYWRIGHT_DOWNLOAD_HOST PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsers npx playwright install chromium; then - browser_ok=1 - break - fi - echo ">>> Browser download attempt $attempt failed. Retrying in 5s..." - sleep 5 -done - -if [[ $browser_ok -ne 1 ]]; then - emit 06_playwright error "Failed to download Chromium browser" - exit 1 -fi +PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsers npx playwright install chromium # Allow PDF operations in ImageMagick (if restricted) if [[ -f /etc/ImageMagick-6/policy.xml ]] && \ diff --git a/libs/hexagent/sandbox/vm/setup_lite/setup.sh b/libs/hexagent/sandbox/vm/setup_lite/setup.sh new file mode 100755 index 00000000..071ecc58 --- /dev/null +++ b/libs/hexagent/sandbox/vm/setup_lite/setup.sh @@ -0,0 +1,458 @@ +#!/bin/bash +# ============================================================================= +# HexAgent VM Setup (Lite) — Orchestrator +# ============================================================================= +# Lite variant: minimal baseline packages for demo/Electron deployments. +# Discovers and runs step scripts in order with progress reporting, +# resumability (marker files), concurrency protection (flock), and +# heartbeat for long-running operations. +# +# Usage: +# sudo bash setup.sh # Run all steps (skip completed) +# sudo bash setup.sh --force # Re-run all steps ignoring markers +# sudo bash setup.sh --step 05_pip # Run a single step +# sudo bash setup.sh --list # Show step status +# sudo bash setup.sh --reset # Clear all markers +# +# Progress protocol (stdout): +# @@SETUP::: +# Statuses: start, progress, done, skip, error, heartbeat +# ============================================================================= + +set -uo pipefail +# No -e: we handle errors per-step. + +# ── Constants ──────────────────────────────────────────────────────────────── +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +STEPS_DIR="${SCRIPT_DIR}/steps" +MARKER_DIR="/var/lib/hexagent/setup" +LOG_DIR="/var/log/hexagent/setup" +LOCK_FILE="/var/run/hexagent-setup.lock" +LOCK_FD=9 + +# ── Environment (inherited by steps) ──────────────────────────────────────── +export DEBIAN_FRONTEND=noninteractive +export PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsers +export MARKER_DIR LOG_DIR +if grep -qi microsoft /proc/version 2>/dev/null; then + _OPENAGENT_DEFAULT_CN_MIRRORS=1 +else + _OPENAGENT_DEFAULT_CN_MIRRORS=0 +fi +OPENAGENT_USE_CN_MIRRORS="${OPENAGENT_USE_CN_MIRRORS:-${_OPENAGENT_DEFAULT_CN_MIRRORS}}" +OPENAGENT_APT_MIRROR="${OPENAGENT_APT_MIRROR:-https://mirrors.ustc.edu.cn/ubuntu}" +OPENAGENT_APT_PORTS_MIRROR="${OPENAGENT_APT_PORTS_MIRROR:-https://mirrors.ustc.edu.cn/ubuntu-ports}" +OPENAGENT_PIP_INDEX_URL="${OPENAGENT_PIP_INDEX_URL:-https://pypi.tuna.tsinghua.edu.cn/simple}" +OPENAGENT_NPM_REGISTRY="${OPENAGENT_NPM_REGISTRY:-https://registry.npmmirror.com}" +OPENAGENT_PLAYWRIGHT_DOWNLOAD_HOST="${OPENAGENT_PLAYWRIGHT_DOWNLOAD_HOST:-https://npmmirror.com/mirrors/playwright}" +export OPENAGENT_USE_CN_MIRRORS OPENAGENT_APT_MIRROR OPENAGENT_APT_PORTS_MIRROR +export OPENAGENT_PIP_INDEX_URL OPENAGENT_NPM_REGISTRY OPENAGENT_PLAYWRIGHT_DOWNLOAD_HOST + +# ── CLI defaults ───────────────────────────────────────────────────────────── +FORCE=false +SINGLE_STEP="" +LIST_ONLY=false +RESET=false + +# ── CLI parsing ────────────────────────────────────────────────────────────── +usage() { + echo "Usage: sudo bash setup.sh [OPTIONS]" + echo "" + echo "Options:" + echo " --force Re-run all steps (ignore markers)" + echo " --step Run a single step (e.g. --step 05_pip)" + echo " --list Show steps and their completion status" + echo " --reset Clear all markers, then exit" + echo " -h, --help Show this help" +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --force) FORCE=true; shift ;; + --step) SINGLE_STEP="$2"; shift 2 ;; + --list) LIST_ONLY=true; shift ;; + --reset) RESET=true; shift ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown option: $1"; usage; exit 1 ;; + esac +done + +# ── Root check ─────────────────────────────────────────────────────────────── +if [[ "$(id -u)" -ne 0 ]]; then + echo "ERROR: Must run as root (sudo)." >&2 + exit 1 +fi + +# ── Directory setup ────────────────────────────────────────────────────────── +mkdir -p "$MARKER_DIR" "$LOG_DIR" + +# ── emit() — progress protocol ────────────────────────────────────────────── +# Writes to fd 3 which points to the original stdout (what the backend reads). +# All other output (package managers) goes to the log file. +emit() { + # Usage: emit + local step_id="$1" status="$2" message="${3:-}" + printf '@@SETUP:%s:%s:%s\n' "$step_id" "$status" "$message" >&3 +} +export -f emit + +# ── apt_install — retry wrapper ────────────────────────────────────────────── +apt_install() { + local max_attempts=5 + local delay=3 + local attempt=1 + + while [[ $attempt -le $max_attempts ]]; do + echo ">>> apt-get install attempt $attempt/$max_attempts" + if [[ $attempt -eq 1 ]]; then + apt-get install -y --no-install-recommends "$@" && return 0 + else + apt-get install -y --no-install-recommends --fix-missing "$@" && return 0 + fi + + echo ">>> Attempt $attempt failed. Retrying in ${delay}s..." + sleep $delay + dpkg --configure -a || true + apt-get install -y -f || true + delay=$((delay * 2)) + attempt=$((attempt + 1)) + done + + # Final fallback: per-package download then bulk install + echo ">>> Bulk install failed. Falling back to per-package download..." + dpkg --configure -a || true + apt-get install -y -f || true + + local pkg + for pkg in "$@"; do + local pkg_attempt=1 + while [[ $pkg_attempt -le 3 ]]; do + apt-get install -y --no-install-recommends -d "$pkg" 2>/dev/null && break + echo ">>> Download failed for $pkg (attempt $pkg_attempt/3)" + sleep $((pkg_attempt * 2)) + pkg_attempt=$((pkg_attempt + 1)) + done + done + + echo ">>> Installing all packages from local cache..." + if apt-get install -y --no-install-recommends "$@"; then + return 0 + fi + + echo ">>> ERROR: apt-get install failed after all retries" + echo ">>> Failed packages: $*" + return 1 +} +export -f apt_install + +# ── pip_install — retry wrapper ────────────────────────────────────────────── +pip_install() { + local max_attempts=5 + local delay=5 + local attempt=1 + local use_cn_mirrors="${OPENAGENT_USE_CN_MIRRORS:-0}" + local pip_opts=( + --timeout 120 + --retries 3 + --no-cache-dir + ) + if pip3 help install 2>/dev/null | grep -q -- "--break-system-packages"; then + pip_opts+=(--break-system-packages) + fi + + while [[ $attempt -le $max_attempts ]]; do + echo ">>> pip install attempt $attempt/$max_attempts (${#} packages)" + if pip3 install "${pip_opts[@]}" "$@"; then + return 0 + fi + if [[ "$use_cn_mirrors" == "1" ]]; then + echo ">>> Mirror install failed, retrying with official PyPI..." + if PIP_INDEX_URL="https://pypi.org/simple" \ + PIP_EXTRA_INDEX_URL="" \ + pip3 install "${pip_opts[@]}" "$@"; then + return 0 + fi + fi + echo ">>> Attempt $attempt failed. Retrying in ${delay}s..." + sleep $delay + delay=$((delay * 2)) + attempt=$((attempt + 1)) + done + + # Final fallback: install one at a time + echo ">>> Batch install failed. Falling back to per-package install..." + local pkg failed=() + for pkg in "$@"; do + local pkg_attempt=1 + local pkg_ok=false + while [[ $pkg_attempt -le 3 ]]; do + if pip3 install "${pip_opts[@]}" "$pkg" 2>&1; then + pkg_ok=true + break + fi + if [[ "$use_cn_mirrors" == "1" ]]; then + if PIP_INDEX_URL="https://pypi.org/simple" \ + PIP_EXTRA_INDEX_URL="" \ + pip3 install "${pip_opts[@]}" "$pkg" 2>&1; then + pkg_ok=true + break + fi + fi + echo ">>> Failed: $pkg (attempt $pkg_attempt/3)" + sleep $((pkg_attempt * 3)) + pkg_attempt=$((pkg_attempt + 1)) + done + if [[ "$pkg_ok" == false ]]; then + failed+=("$pkg") + fi + done + + if [[ ${#failed[@]} -gt 0 ]]; then + echo ">>> ERROR: These packages failed after all retries:" + printf '>>> %s\n' "${failed[@]}" + return 1 + fi + return 0 +} +export -f pip_install + +configure_cn_mirrors() { + if [[ "${OPENAGENT_USE_CN_MIRRORS}" != "1" ]]; then + return 0 + fi + + emit _meta progress "Applying China mirrors (APT/PIP/NPM/Playwright)" + + # sed replacement escapes for arbitrary mirror strings (e.g. containing '&' or '|') + local apt_mirror_esc apt_ports_mirror_esc + apt_mirror_esc="${OPENAGENT_APT_MIRROR//\\/\\\\}" + apt_mirror_esc="${apt_mirror_esc//&/\\&}" + apt_mirror_esc="${apt_mirror_esc//|/\\|}" + apt_ports_mirror_esc="${OPENAGENT_APT_PORTS_MIRROR//\\/\\\\}" + apt_ports_mirror_esc="${apt_ports_mirror_esc//&/\\&}" + apt_ports_mirror_esc="${apt_ports_mirror_esc//|/\\|}" + + if [[ -f /etc/apt/sources.list ]]; then + cp -n /etc/apt/sources.list /etc/apt/sources.list.openagent.bak 2>/dev/null || true + sed -Ei \ + -e "s|https?://(archive|security)\.ubuntu\.com/ubuntu|${apt_mirror_esc}|g" \ + -e "s|https?://ports\.ubuntu\.com/ubuntu-ports|${apt_ports_mirror_esc}|g" \ + /etc/apt/sources.list 2>/dev/null || true + fi + + if [[ -f /etc/apt/sources.list.d/ubuntu.sources ]]; then + cp -n /etc/apt/sources.list.d/ubuntu.sources /etc/apt/sources.list.d/ubuntu.sources.openagent.bak 2>/dev/null || true + sed -Ei \ + -e "s|^URIs:[[:space:]]*https?://(archive|security)\.ubuntu\.com/ubuntu/?$|URIs: ${apt_mirror_esc}|g" \ + -e "s|^URIs:[[:space:]]*https?://ports\.ubuntu\.com/ubuntu-ports/?$|URIs: ${apt_ports_mirror_esc}|g" \ + /etc/apt/sources.list.d/ubuntu.sources 2>/dev/null || true + fi + + cat >/etc/pip.conf </etc/profile.d/openagent-mirrors.sh < "${MARKER_DIR}/$1.done"; } + +# ── Step discovery ─────────────────────────────────────────────────────────── +discover_steps() { + for f in "${STEPS_DIR}"/*.sh; do + [[ -f "$f" ]] || continue + echo "$f" + done | sort +} + +# Get step description from the second line (# comment) of a step file. +step_desc() { + sed -n '2s/^# *//p' "$1" +} + +# ── --reset ────────────────────────────────────────────────────────────────── +if [[ "$RESET" == true ]]; then + rm -f "${MARKER_DIR}"/*.done + echo "All markers cleared." + exit 0 +fi + +# ── --list ─────────────────────────────────────────────────────────────────── +if [[ "$LIST_ONLY" == true ]]; then + while IFS= read -r step_file; do + step_id="$(basename "$step_file" .sh)" + if step_done "$step_id"; then + echo "[done] $step_id ($(cat "${MARKER_DIR}/${step_id}.done"))" + else + echo "[pending] $step_id — $(step_desc "$step_file")" + fi + done < <(discover_steps) + exit 0 +fi + +# ── Concurrency lock ───────────────────────────────────────────────────────── +exec 9>"$LOCK_FILE" +if ! flock -n $LOCK_FD; then + echo "ERROR: Another setup instance is running (lockfile: $LOCK_FILE)" >&2 + exit 1 +fi + +# ── fd redirection ─────────────────────────────────────────────────────────── +# fd 3 = original stdout → backend reads @@SETUP: lines from here +# stdout + stderr → log file (all package manager noise) +LOGFILE="${LOG_DIR}/setup-$(date +%Y%m%d-%H%M%S).log" +exec 3>&1 +exec 1>>"$LOGFILE" 2>&1 + +# ── Signal handling ────────────────────────────────────────────────────────── +HEARTBEAT_PID="" + +cleanup() { + [[ -n "$HEARTBEAT_PID" ]] && kill "$HEARTBEAT_PID" 2>/dev/null || true + flock -u $LOCK_FD 2>/dev/null || true + rm -f "$LOCK_FILE" +} +trap cleanup EXIT + +cancelled() { + emit _meta error "Cancelled by signal" + exit 130 +} +trap cancelled SIGTERM SIGINT + +# ── Heartbeat ──────────────────────────────────────────────────────────────── +start_heartbeat() { + local step_id="$1" + ( + while true; do + sleep 15 + emit "$step_id" heartbeat "" + done + ) & + HEARTBEAT_PID=$! +} + +stop_heartbeat() { + if [[ -n "$HEARTBEAT_PID" ]]; then + kill "$HEARTBEAT_PID" 2>/dev/null || true + wait "$HEARTBEAT_PID" 2>/dev/null || true + HEARTBEAT_PID="" + fi +} + +# ── Preflight ──────────────────────────────────────────────────────────────── +preflight() { + emit _meta start "Preflight checks" + + # Architecture + ARCH="$(uname -m)" + case "$ARCH" in + x86_64|aarch64) ;; + *) emit _meta error "Unsupported architecture: $ARCH"; exit 1 ;; + esac + export ARCH + + configure_cn_mirrors + + # Disk space (require >= 10 GB free on /) + local free_kb + free_kb=$(df / --output=avail | tail -1 | tr -d ' ') + if (( free_kb < 10485760 )); then + emit _meta error "Insufficient disk space: $((free_kb / 1024))MB free, need 10GB+" + exit 1 + fi + + emit _meta done "Preflight OK (arch=$ARCH, free=$((free_kb / 1024))MB)" +} + +# ── run_step ───────────────────────────────────────────────────────────────── +run_step() { + local step_file="$1" + local step_id + step_id="$(basename "$step_file" .sh)" + + # Skip if already completed (unless --force) + if [[ "$FORCE" != true ]] && step_done "$step_id"; then + emit "$step_id" skip "Already completed ($(cat "${MARKER_DIR}/${step_id}.done"))" + return 0 + fi + + local desc + desc="$(step_desc "$step_file")" + emit "$step_id" start "${desc:-$step_id}" + + start_heartbeat "$step_id" + local start_ts + start_ts=$(date +%s) + + # Run step in a subshell so it inherits exported functions + fd 3 + # but cannot kill the orchestrator on failure. + ( source "$step_file" ) + local rc=$? + + stop_heartbeat + + local elapsed=$(( $(date +%s) - start_ts )) + + if [[ $rc -eq 0 ]]; then + mark_done "$step_id" + emit "$step_id" done "Completed in ${elapsed}s" + else + emit "$step_id" error "Failed (exit $rc) after ${elapsed}s — see $LOGFILE" + return $rc + fi +} + +# ── Main ───────────────────────────────────────────────────────────────────── +main() { + preflight + + # Count total steps + local total=0 + while IFS= read -r _; do + total=$((total + 1)) + done < <(discover_steps) + emit _meta progress "total_steps=$total" + + # Single step mode + if [[ -n "$SINGLE_STEP" ]]; then + local target="${STEPS_DIR}/${SINGLE_STEP}.sh" + if [[ ! -f "$target" ]]; then + emit _meta error "Step not found: $SINGLE_STEP" + exit 1 + fi + run_step "$target" + exit $? + fi + + # Run all steps in order + local failed=0 + while IFS= read -r step_file; do + if ! run_step "$step_file"; then + failed=1 + break + fi + done < <(discover_steps) + + if [[ $failed -gt 0 ]]; then + emit _meta error "Setup failed — re-run to resume from failed step" + exit 1 + fi + + emit _meta done "All steps complete" +} + +main diff --git a/libs/hexagent/sandbox/vm/setup_lite/steps/01_base.sh b/libs/hexagent/sandbox/vm/setup_lite/steps/01_base.sh new file mode 100755 index 00000000..a52dc1a3 --- /dev/null +++ b/libs/hexagent/sandbox/vm/setup_lite/steps/01_base.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# Base prerequisites +set -euo pipefail + +emit 01_base progress "Running apt-get update" +apt-get update + +emit 01_base progress "Installing ca-certificates, curl, gnupg" +apt_install ca-certificates curl gnupg diff --git a/libs/hexagent/sandbox/vm/setup_lite/steps/02_nodejs.sh b/libs/hexagent/sandbox/vm/setup_lite/steps/02_nodejs.sh new file mode 100755 index 00000000..2bc68aa6 --- /dev/null +++ b/libs/hexagent/sandbox/vm/setup_lite/steps/02_nodejs.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# Node.js 22.x (NodeSource with tarball fallback) +set -euo pipefail + +NODE_MAJOR=22 + +# --- Attempt 1: NodeSource apt repo (retried) --- +nodesource_ok=false +for attempt in 1 2 3; do + emit 02_nodejs progress "NodeSource setup attempt $attempt/3" + if curl -fsSL --retry 3 --retry-delay 5 \ + "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | bash -; then + if apt-get install -y nodejs; then + nodesource_ok=true + break + fi + fi + echo ">>> Attempt $attempt failed. Retrying in $((attempt * 5))s..." + sleep $((attempt * 5)) +done + +# --- Attempt 2: Official binary tarball --- +if [[ "$nodesource_ok" == false ]]; then + emit 02_nodejs progress "Falling back to official binary tarball" + apt-get remove -y nodejs npm 2>/dev/null || true + + case "$ARCH" in + x86_64) NODE_ARCH="x64" ;; + aarch64) NODE_ARCH="arm64" ;; + *) echo "ERROR: Unsupported architecture: $ARCH"; exit 1 ;; + esac + + NODE_VERSION=$(curl -fsSL --retry 3 \ + "https://nodejs.org/dist/latest-v${NODE_MAJOR}.x/" \ + | grep -oP 'node-v\K[0-9]+\.[0-9]+\.[0-9]+' | head -1) + + if [[ -z "$NODE_VERSION" ]]; then + echo "ERROR: Could not determine latest Node.js ${NODE_MAJOR}.x version" + exit 1 + fi + + TARBALL="node-v${NODE_VERSION}-linux-${NODE_ARCH}.tar.xz" + URL="https://nodejs.org/dist/v${NODE_VERSION}/${TARBALL}" + + emit 02_nodejs progress "Downloading Node.js v${NODE_VERSION} for ${NODE_ARCH}" + curl -fsSL --retry 3 --retry-delay 5 -o "/tmp/${TARBALL}" "$URL" + tar -xJf "/tmp/${TARBALL}" -C /usr/local --strip-components=1 + rm -f "/tmp/${TARBALL}" +fi + +# --- Verify --- +node --version +npm --version + +if ! command -v npm >/dev/null 2>&1; then + echo "ERROR: npm is not available after Node.js installation" + exit 1 +fi + +emit 02_nodejs progress "Node.js $(node --version) with npm $(npm --version) installed" diff --git a/libs/hexagent/sandbox/vm/setup_lite/steps/03_apt.sh b/libs/hexagent/sandbox/vm/setup_lite/steps/03_apt.sh new file mode 100755 index 00000000..82bec2e1 --- /dev/null +++ b/libs/hexagent/sandbox/vm/setup_lite/steps/03_apt.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# System packages (minimal baseline; install extras on demand) +set -uo pipefail +# No -e: apt_install handles its own errors with retries. + +apt-get update + +emit 03_apt progress "group=core_utils" +apt_install \ + bash coreutils wget curl git zip unzip jq tree ripgrep \ + file findutils patch sqlite3 || exit 1 + +emit 03_apt progress "group=build_tools" +apt_install build-essential pkg-config || exit 1 + +emit 03_apt progress "group=python" +apt_install python3 python3-dev python3-pip python3-venv pipx || exit 1 + +emit 03_apt progress "group=media" +apt_install imagemagick graphviz || exit 1 + +emit 03_apt progress "group=fonts" +apt_install \ + fonts-liberation2 fonts-dejavu || exit 1 + +emit 03_apt progress "group=dev_libs" +apt_install \ + libffi-dev zlib1g-dev libpng-dev libfreetype-dev libbz2-dev || exit 1 + +# Cleanup +emit 03_apt progress "Cleaning apt cache" +rm -rf /var/lib/apt/lists/* +apt-get autoremove -y +apt-get clean + +# Verify nothing is broken +if ! dpkg --audit 2>/dev/null || dpkg -l | grep -q '^iF'; then + echo ">>> WARNING: Some packages are in a broken state" + dpkg -l | grep '^iF' || true + exit 1 +fi diff --git a/libs/hexagent/sandbox/vm/setup_lite/steps/04_npm.sh b/libs/hexagent/sandbox/vm/setup_lite/steps/04_npm.sh new file mode 100755 index 00000000..014e24f0 --- /dev/null +++ b/libs/hexagent/sandbox/vm/setup_lite/steps/04_npm.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# NPM global packages (minimal baseline; install extras on demand) +set -euo pipefail + +if [[ "${OPENAGENT_USE_CN_MIRRORS:-0}" == "1" ]]; then + emit 04_npm progress "Configuring npm registry mirror" + npm config set registry "${OPENAGENT_NPM_REGISTRY}" >/dev/null 2>&1 || true +fi + +emit 04_npm progress "Installing npm global packages" +npm install -g typescript tsx playwright diff --git a/libs/hexagent/sandbox/vm/setup_lite/steps/05_pip.sh b/libs/hexagent/sandbox/vm/setup_lite/steps/05_pip.sh new file mode 100755 index 00000000..1960556a --- /dev/null +++ b/libs/hexagent/sandbox/vm/setup_lite/steps/05_pip.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# Python packages (minimal baseline; install extras on demand) +set -euo pipefail + +emit 05_pip progress "Core data & visualization" +pip_install numpy pandas matplotlib pillow + +emit 05_pip progress "Web & HTTP" +pip_install requests beautifulsoup4 lxml + +emit 05_pip progress "Utilities" +pip_install \ + uv click pyyaml python-dotenv tabulate + +# Cleanup +emit 05_pip progress "Cleaning pip cache" +pip3 cache purge +rm -rf /root/.cache/pip /tmp/pip-* 2>/dev/null || true diff --git a/libs/hexagent/sandbox/vm/setup_lite/steps/06_playwright.sh b/libs/hexagent/sandbox/vm/setup_lite/steps/06_playwright.sh new file mode 100755 index 00000000..c1b344b8 --- /dev/null +++ b/libs/hexagent/sandbox/vm/setup_lite/steps/06_playwright.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# Playwright browsers + ImageMagick policy +set -euo pipefail + +# Recover any broken dpkg/apt state from prior steps +dpkg --configure -a || true +apt-get install -y -f || true + +# Install Playwright OS deps (apt) retry-wrapped +max_attempts=5 +deps_ok=0 +for ((attempt = 1; attempt <= max_attempts; attempt++)); do + emit 06_playwright progress "install-deps attempt $attempt/$max_attempts" + if npx playwright install-deps chromium; then + deps_ok=1 + break + fi + echo ">>> Retrying in 5s..." + sleep 5 + dpkg --configure -a || true + apt-get install -y -f || true + apt-get update || true +done + +if [[ $deps_ok -ne 1 ]]; then + emit 06_playwright error "Failed to install Playwright system dependencies" + exit 1 +fi + +# Download Chromium binary +emit 06_playwright progress "Downloading Chromium binary" +mirror_host="${OPENAGENT_PLAYWRIGHT_DOWNLOAD_HOST:-}" +browser_ok=0 + +# If bundled browsers are already present in the VM image, skip network download. +if find /opt/pw-browsers -type f \( -name "chrome-headless-shell" -o -name "chrome" \) 2>/dev/null | grep -q .; then + emit 06_playwright progress "Bundled Playwright browser detected under /opt/pw-browsers, skipping download" + browser_ok=1 +fi + +for ((attempt = 1; attempt <= max_attempts; attempt++)); do + if [[ $browser_ok -eq 1 ]]; then + break + fi + + if [[ "${OPENAGENT_USE_CN_MIRRORS:-0}" == "1" && -n "$mirror_host" ]]; then + emit 06_playwright progress "browser install attempt $attempt/$max_attempts (mirror)" + if PLAYWRIGHT_DOWNLOAD_HOST="$mirror_host" \ + PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsers \ + npx playwright install chromium; then + browser_ok=1 + break + fi + emit 06_playwright progress "Mirror unavailable, retrying with official host" + else + emit 06_playwright progress "browser install attempt $attempt/$max_attempts" + fi + + if env -u PLAYWRIGHT_DOWNLOAD_HOST PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsers npx playwright install chromium; then + browser_ok=1 + break + fi + echo ">>> Browser download attempt $attempt failed. Retrying in 5s..." + sleep 5 +done + +if [[ $browser_ok -ne 1 ]]; then + emit 06_playwright error "Failed to download Chromium browser" + exit 1 +fi + +# Allow PDF operations in ImageMagick (if restricted) +if [[ -f /etc/ImageMagick-6/policy.xml ]] && \ + grep -q 'rights="none" pattern="PDF"' /etc/ImageMagick-6/policy.xml; then + sed -i 's/rights="none" pattern="PDF"/rights="read|write" pattern="PDF"/' \ + /etc/ImageMagick-6/policy.xml + emit 06_playwright progress "ImageMagick PDF policy updated" +fi diff --git a/libs/hexagent/sandbox/vm/setup_lite/steps/07_finalize.sh b/libs/hexagent/sandbox/vm/setup_lite/steps/07_finalize.sh new file mode 100755 index 00000000..5552893c --- /dev/null +++ b/libs/hexagent/sandbox/vm/setup_lite/steps/07_finalize.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# Symlinks and session directory +set -euo pipefail + +emit 07_finalize progress "Creating python symlink" +ln -sf /usr/bin/python3 /usr/bin/python + +emit 07_finalize progress "Creating /sessions directory" +mkdir -p /sessions +chmod 755 /sessions diff --git a/libs/hexagent/sandbox/vm/setup_lite/steps/08_cleanup.sh b/libs/hexagent/sandbox/vm/setup_lite/steps/08_cleanup.sh new file mode 100755 index 00000000..e78b631c --- /dev/null +++ b/libs/hexagent/sandbox/vm/setup_lite/steps/08_cleanup.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# Cache cleanup to minimize image size +set -euo pipefail + +emit 08_cleanup progress "Cleaning apt cache" +apt-get clean + +emit 08_cleanup progress "Removing temporary files" +rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 2>/dev/null || true +rm -rf /root/.cache /root/.npm/_cacache 2>/dev/null || true diff --git a/libs/hexagent/tests/unit_tests/computer/test_local_vm_win.py b/libs/hexagent/tests/unit_tests/computer/test_local_vm_win.py index f8fb5e71..6bccba0f 100644 --- a/libs/hexagent/tests/unit_tests/computer/test_local_vm_win.py +++ b/libs/hexagent/tests/unit_tests/computer/test_local_vm_win.py @@ -191,7 +191,8 @@ async def test_mount_idempotent_self_heals_missing_live_mount(self, tmp_path: An ] ) - await mgr.mount(Mount(source=str(d), target="code")) + with patch("hexagent.computer.local._wsl._win_path_to_wsl", return_value="/mnt/c/code"): + await mgr.mount(Mount(source=str(d), target="code")) vm.apply_mounts.assert_not_awaited() assert vm.shell.await_count == 3 diff --git a/libs/hexagent/tests/unit_tests/computer/test_vm.py b/libs/hexagent/tests/unit_tests/computer/test_vm.py index 05e6dc37..90e0a7eb 100644 --- a/libs/hexagent/tests/unit_tests/computer/test_vm.py +++ b/libs/hexagent/tests/unit_tests/computer/test_vm.py @@ -195,6 +195,7 @@ async def test_run_translates_vm_error_to_cli_error(self) -> None: with pytest.raises(CLIError, match="boom"): await computer.run("bad") + class TestUpload: """Tests for upload().""" diff --git a/libs/hexagent/tests/unit_tests/computer/test_wsl.py b/libs/hexagent/tests/unit_tests/computer/test_wsl.py index 36f90da2..f2ed9766 100644 --- a/libs/hexagent/tests/unit_tests/computer/test_wsl.py +++ b/libs/hexagent/tests/unit_tests/computer/test_wsl.py @@ -214,8 +214,8 @@ async def test_new_session_defaults_to_session_home(self) -> None: vm._vm = backend vm._instance = "openagent" vm._lock = asyncio.Lock() - vm._generate_unique_name = AsyncMock(return_value="alice") - vm._create_user = AsyncMock() + vm._generate_unique_name = AsyncMock(return_value="alice") # type: ignore[method-assign] + vm._create_user = AsyncMock() # type: ignore[method-assign] computer = await LocalVM.computer(vm) await computer.run("pwd") diff --git a/libs/hexagent/tests/unit_tests/harness/test_environment.py b/libs/hexagent/tests/unit_tests/harness/test_environment.py index 202d7b6f..a48fa284 100644 --- a/libs/hexagent/tests/unit_tests/harness/test_environment.py +++ b/libs/hexagent/tests/unit_tests/harness/test_environment.py @@ -6,8 +6,6 @@ from datetime import UTC, datetime from unittest.mock import AsyncMock -import pytest - from hexagent.harness.environment import EnvironmentResolver from hexagent.types import CLIResult diff --git a/libs/hexagent_demo/backend/hexagent_api/paths.py b/libs/hexagent_demo/backend/hexagent_api/paths.py index 2e252fd0..d2923c40 100644 --- a/libs/hexagent_demo/backend/hexagent_api/paths.py +++ b/libs/hexagent_demo/backend/hexagent_api/paths.py @@ -111,3 +111,13 @@ def vm_setup_dir() -> Path: system (Lima, WSL, cloud VMs, etc.). """ return vm_dir() / "setup" + + +def vm_setup_lite_dir() -> Path: + """Lite VM setup scripts (``sandbox/vm/setup_lite/``). + + Minimal baseline variant for demo/Electron deployments. Installs only + the packages needed for the demo and defers heavier dependencies to + on-demand installation. Supports China-region mirrors. + """ + return vm_dir() / "setup_lite" diff --git a/libs/hexagent_demo/backend/hexagent_api/routes/setup.py b/libs/hexagent_demo/backend/hexagent_api/routes/setup.py index fbddd5d4..a12ba5c7 100644 --- a/libs/hexagent_demo/backend/hexagent_api/routes/setup.py +++ b/libs/hexagent_demo/backend/hexagent_api/routes/setup.py @@ -27,7 +27,7 @@ from fastapi import APIRouter, HTTPException from fastapi.responses import Response, StreamingResponse -from hexagent_api.paths import data_dir, deps_dir, vm_lima_dir, vm_setup_dir +from hexagent_api.paths import data_dir, deps_dir, vm_lima_dir, vm_setup_dir, vm_setup_lite_dir logger = logging.getLogger(__name__) @@ -1227,7 +1227,7 @@ async def _run_lima(self, **kwargs: object) -> None: # 2. Copy setup directory into VM self._emit("progress", {"step": "copying", "message": "Copying setup files to VM..."}) - setup_dir = vm_setup_dir() + setup_dir = vm_setup_lite_dir() if not setup_dir.is_dir(): self._emit("error", {"message": f"Setup directory not found: {setup_dir}"}) self._status = "error" @@ -1238,7 +1238,7 @@ async def _run_lima(self, **kwargs: object) -> None: with tempfile.TemporaryDirectory(prefix="hexagent_setup_") as tmp: tar_path = os.path.join(tmp, "setup.tar.gz") _sp.run( - ["tar", "-czf", tar_path, "-C", str(setup_dir.parent), "setup"], + ["tar", "-czf", tar_path, "-C", str(setup_dir.parent), setup_dir.name], check=True, ) copy_proc = await asyncio.create_subprocess_exec( @@ -1334,7 +1334,7 @@ async def _run_wsl(self, **kwargs: object) -> None: return self._emit("progress", {"step": "copying", "message": "Preparing setup files in WSL..."}) - setup_dir = vm_setup_dir() + setup_dir = vm_setup_lite_dir() if not setup_dir.is_dir(): self._emit("error", {"message": f"Setup directory not found: {setup_dir}"}) self._status = "error" diff --git a/libs/openagent/sandbox/vm/wsl/prebuilt/openagent-prebuilt.tar b/libs/openagent/sandbox/vm/wsl/prebuilt/openagent-prebuilt.tar deleted file mode 100644 index 717a5a6d..00000000 --- a/libs/openagent/sandbox/vm/wsl/prebuilt/openagent-prebuilt.tar +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cb39247403141d4bd20516ce9fbb25d5956a11f838e577959a7bed4bc6514a79 -size 2019061760 diff --git a/reports/vm-prebuilt-inventory-20260325/apt-installed-new.txt b/reports/vm-prebuilt-inventory-20260325/apt-installed-new.txt deleted file mode 100644 index 42f8bd6ff8c23dc3f52b2957a960fff46f4aa2c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3090 ycmezW&z8ZKftP^`NRHB@;V_yGM)SdFIWSrdjFtnV<-lk;Fj@|bmIEWL8~^|WaonH) diff --git a/reports/vm-prebuilt-inventory-20260325/apt-installed.txt b/reports/vm-prebuilt-inventory-20260325/apt-installed.txt deleted file mode 100644 index df481158..00000000 --- a/reports/vm-prebuilt-inventory-20260325/apt-installed.txt +++ /dev/null @@ -1,964 +0,0 @@ -adduser==3.137ubuntu1 -adwaita-icon-theme==46.0-1 -apparmor==4.0.1really4.0.1-0ubuntu0.24.04.5 -apport==2.28.1-0ubuntu3.8 -apport-core-dump-handler==2.28.1-0ubuntu3.8 -apport-symptoms==0.25 -appstream==1.0.2-1build6 -apt==2.8.3 -apt-transport-https==2.8.3 -apt-utils==2.8.3 -at-spi2-common==2.52.0-1build1 -at-spi2-core==2.52.0-1build1 -base-files==13ubuntu10.4 -base-passwd==3.6.3build1 -bash==5.2.21-2ubuntu4 -bash-completion==1:2.11-8 -bc==1.07.1-3ubuntu4 -binutils==2.42-4ubuntu2.8 -binutils-common==2.42-4ubuntu2.8 -binutils-x86-64-linux-gnu==2.42-4ubuntu2.8 -bsdextrautils==2.39.3-9ubuntu6.5 -bsdutils==1:2.39.3-9ubuntu6.5 -build-essential==12.10ubuntu1 -byobu==6.11-0ubuntu1 -bzip2==1.0.8-5.1build0.1 -ca-certificates==20240203 -ca-certificates-java==20240118 -cloud-guest-utils==0.33-1 -cloud-init==25.2-0ubuntu1~24.04.1 -command-not-found==23.04.0 -console-setup==1.226ubuntu1 -console-setup-linux==1.226ubuntu1 -coreutils==9.4-3ubuntu6.2 -cpp==4:13.2.0-7ubuntu1 -cpp-13==13.3.0-6ubuntu2~24.04.1 -cpp-13-x86-64-linux-gnu==13.3.0-6ubuntu2~24.04.1 -cpp-x86-64-linux-gnu==4:13.2.0-7ubuntu1 -cron==3.0pl1-184ubuntu2 -cron-daemon-common==3.0pl1-184ubuntu2 -curl==8.5.0-2ubuntu10.8 -dash==0.5.12-6ubuntu5 -dbus==1.14.10-4ubuntu4.1 -dbus-bin==1.14.10-4ubuntu4.1 -dbus-daemon==1.14.10-4ubuntu4.1 -dbus-session-bus-common==1.14.10-4ubuntu4.1 -dbus-system-bus-common==1.14.10-4ubuntu4.1 -dbus-user-session==1.14.10-4ubuntu4.1 -dbus-x11==1.14.10-4ubuntu4.1 -dconf-gsettings-backend==0.40.0-4ubuntu0.1 -dconf-service==0.40.0-4ubuntu0.1 -debconf==1.5.86ubuntu1 -debconf-i18n==1.5.86ubuntu1 -debianutils==5.17build1 -default-jre-headless==2:1.21-75+exp1 -dhcpcd-base==1:10.0.6-1ubuntu3.2 -diffutils==1:3.10-1build1 -dirmngr==2.4.4-2ubuntu17.4 -distro-info==1.7build1 -distro-info-data==0.60ubuntu0.5 -dmsetup==2:1.02.185-3ubuntu3.2 -dpkg==1.22.6ubuntu6.5 -dpkg-dev==1.22.6ubuntu6.5 -e2fsprogs==1.47.0-2.4~exp1ubuntu4.1 -e2fsprogs-l10n==1.47.0-2.4~exp1ubuntu4.1 -eatmydata==131-1ubuntu1 -ed==1.20.1-1 -eject==2.39.3-9ubuntu6.5 -ethtool==1:6.7-1build1 -fdisk==2.39.3-9ubuntu6.5 -ffmpeg==7:6.1.1-3ubuntu5 -file==1:5.45-3build1 -findutils==4.9.0-5build1 -fontconfig==2.15.0-1.1ubuntu2 -fontconfig-config==2.15.0-1.1ubuntu2 -fonts-crosextra-caladea==20200211-2 -fonts-crosextra-carlito==20230309-2 -fonts-dejavu==2.37-8 -fonts-dejavu-core==2.37-8 -fonts-dejavu-extra==2.37-8 -fonts-dejavu-mono==2.37-8 -fonts-freefont-ttf==20211204+svn4273-2 -fonts-gfs-baskerville==1.1-6 -fonts-gfs-porson==1.1-7 -fonts-ipafont-gothic==00303-21ubuntu1 -fonts-liberation==1:2.1.5-3 -fonts-liberation2==1:2.1.5-3 -fonts-lmodern==2.005-1 -fonts-noto-cjk==1:20230817+repack1-3 -fonts-noto-color-emoji==2.047-0ubuntu0.24.04.1 -fonts-opensymbol==4:102.12+LibO24.2.7-0ubuntu0.24.04.4 -fonts-texgyre==20180621-6 -fonts-ubuntu==0.869+git20240321-0ubuntu1 -fonts-urw-base35==20200910-8 -fonts-wqy-zenhei==0.9.45-8 -fuse3==3.14.0-5build1 -g++==4:13.2.0-7ubuntu1 -g++-13==13.3.0-6ubuntu2~24.04.1 -g++-13-x86-64-linux-gnu==13.3.0-6ubuntu2~24.04.1 -g++-x86-64-linux-gnu==4:13.2.0-7ubuntu1 -gawk==1:5.2.1-2build3 -gcc==4:13.2.0-7ubuntu1 -gcc-13==13.3.0-6ubuntu2~24.04.1 -gcc-13-base==13.3.0-6ubuntu2~24.04.1 -gcc-13-x86-64-linux-gnu==13.3.0-6ubuntu2~24.04.1 -gcc-14-base==14.2.0-4ubuntu2~24.04.1 -gcc-x86-64-linux-gnu==4:13.2.0-7ubuntu1 -gdisk==1.0.10-1build1 -gettext-base==0.21-14ubuntu2 -ghostscript==10.02.1~dfsg1-0ubuntu7.8 -gir1.2-girepository-2.0==1.80.1-1 -gir1.2-glib-2.0==2.80.0-6ubuntu3.8 -gir1.2-packagekitglib-1.0==1.2.8-2ubuntu1.4 -git==1:2.43.0-1ubuntu7.3 -git-man==1:2.43.0-1ubuntu7.3 -gnupg==2.4.4-2ubuntu17.4 -gnupg-l10n==2.4.4-2ubuntu17.4 -gnupg-utils==2.4.4-2ubuntu17.4 -gpg==2.4.4-2ubuntu17.4 -gpg-agent==2.4.4-2ubuntu17.4 -gpgconf==2.4.4-2ubuntu17.4 -gpgsm==2.4.4-2ubuntu17.4 -gpgv==2.4.4-2ubuntu17.4 -gpg-wks-client==2.4.4-2ubuntu17.4 -graphviz==2.42.2-9ubuntu0.1 -grep==3.11-4build1 -groff-base==1.23.0-3build2 -gsettings-desktop-schemas==46.1-0ubuntu1 -gtk-update-icon-cache==3.24.41-4ubuntu1.3 -gzip==1.12-1ubuntu3.1 -hicolor-icon-theme==0.17-2 -hostname==3.23+nmu2ubuntu2 -humanity-icon-theme==0.6.16 -imagemagick==8:6.9.12.98+dfsg1-5.2build2 -imagemagick-6.q16==8:6.9.12.98+dfsg1-5.2build2 -imagemagick-6-common==8:6.9.12.98+dfsg1-5.2build2 -info==7.1-3build2 -init==1.66ubuntu1 -init-system-helpers==1.66ubuntu1 -install-info==7.1-3build2 -iproute2==6.1.0-1ubuntu6.2 -iputils-ping==3:20240117-1ubuntu0.1 -iso-codes==4.16.0-1 -java-common==0.75+exp1 -jq==1.7.1-3ubuntu0.24.04.1 -kbd==2.6.4-2ubuntu2 -keyboard-configuration==1.226ubuntu1 -keyboxd==2.4.4-2ubuntu17.4 -kmod==31+20240202-2ubuntu7.1 -krb5-locales==1.20.1-6ubuntu2.6 -landscape-client==24.02-0ubuntu5.7 -landscape-common==24.02-0ubuntu5.7 -latexmk==1:4.83-1 -less==590-2ubuntu2.1 -libabsl20220623t64==20220623.1-3.1ubuntu3.2 -libabw-0.1-1==0.1.3-1build4 -libacl1==2.3.2-1build1.1 -libann0==1.1.2+doc-9build1 -libaom3==3.8.2-2ubuntu0.1 -libapache-pom-java==29-2 -libapparmor1==4.0.1really4.0.1-0ubuntu0.24.04.5 -libappstream5==1.0.2-1build6 -libapt-pkg6.0t64==2.8.3 -libarchive13t64==3.7.2-2ubuntu0.5 -libargon2-1==0~20190702+dfsg-4build1 -libasan8==14.2.0-4ubuntu2~24.04.1 -libasound2-data==1.2.11-1ubuntu0.2 -libasound2t64==1.2.11-1ubuntu0.2 -libass9==1:0.17.1-2build1 -libassuan0==2.5.6-1build1 -libasyncns0==0.8-6build4 -libatk1.0-0t64==2.52.0-1build1 -libatk-bridge2.0-0t64==2.52.0-1build1 -libatm1t64==1:2.5.1-5.1build1 -libatomic1==14.2.0-4ubuntu2~24.04.1 -libatspi2.0-0t64==2.52.0-1build1 -libattr1==1:2.5.2-1build1.1 -libaudit1==1:3.1.2-2.1build1.1 -libaudit-common==1:3.1.2-2.1build1.1 -libavahi-client3==0.8-13ubuntu6.1 -libavahi-common3==0.8-13ubuntu6.1 -libavahi-common-data==0.8-13ubuntu6.1 -libavc1394-0==0.5.4-5build3 -libavcodec60==7:6.1.1-3ubuntu5 -libavdevice60==7:6.1.1-3ubuntu5 -libavfilter9==7:6.1.1-3ubuntu5 -libavformat60==7:6.1.1-3ubuntu5 -libavutil58==7:6.1.1-3ubuntu5 -libbcprov-java==1.77-1 -libbinutils==2.42-4ubuntu2.8 -libblas3==3.12.0-3build1.1 -libblkid1==2.39.3-9ubuntu6.5 -libblkid-dev==2.39.3-9ubuntu6.5 -libbluray2==1:1.3.4-1build1 -libboost-iostreams1.83.0==1.83.0-2.1ubuntu3.2 -libboost-locale1.83.0==1.83.0-2.1ubuntu3.2 -libboost-thread1.83.0==1.83.0-2.1ubuntu3.2 -libbpf1==1:1.3.0-2build2 -libbrotli1==1.1.0-2build2 -libbrotli-dev==1.1.0-2build2 -libbs2b0==3.1.0+dfsg-7build1 -libbsd0==0.12.1-1build1.1 -libbz2-1.0==1.0.8-5.1build0.1 -libbz2-dev==1.0.8-5.1build0.1 -libc6==2.39-0ubuntu8.7 -libc6-dev==2.39-0ubuntu8.7 -libcaca0==0.99.beta20-4ubuntu0.1 -libcairo2==1.18.0-3build1 -libcairo2-dev==1.18.0-3build1 -libcairo-gobject2==1.18.0-3build1 -libcairo-script-interpreter2==1.18.0-3build1 -libcap2==1:2.66-5ubuntu2.2 -libcap2-bin==1:2.66-5ubuntu2.2 -libcap-ng0==0.8.4-2build2 -libc-bin==2.39-0ubuntu8.7 -libcbor0.10==0.10.2-1.2ubuntu2 -libcc1-0==14.2.0-4ubuntu2~24.04.1 -libc-dev-bin==2.39-0ubuntu8.7 -libcdio19t64==2.1.0-4.1ubuntu1.2 -libcdio-cdda2t64==10.2+2.0.1-1.1build2 -libcdio-paranoia2t64==10.2+2.0.1-1.1build2 -libcdr-0.1-1==0.1.7-1build2 -libcdt5==2.42.2-9ubuntu0.1 -libcgraph6==2.42.2-9ubuntu0.1 -libchromaprint1==1.5.1-5 -libcjson1==1.7.17-1 -libclucene-contribs1t64==2.3.3.4+dfsg-1.2ubuntu2 -libclucene-core1t64==2.3.3.4+dfsg-1.2ubuntu2 -libcodec2-1.2==1.2.0-2build1 -libcolamd3==1:7.6.1+dfsg-1build1 -libcolord2==1.4.7-1build2 -libcom-err2==1.47.0-2.4~exp1ubuntu4.1 -libcommons-lang3-java==3.14.0-1 -libcommons-logging-java==1.3.0-1ubuntu1 -libcommons-parent-java==56-1 -libcrypt1==1:4.4.36-4build1 -libcrypt-dev==1:4.4.36-4build1 -libcryptsetup12==2:2.7.0-1ubuntu4.2 -libctf0==2.42-4ubuntu2.8 -libctf-nobfd0==2.42-4ubuntu2.8 -libcups2t64==2.4.7-1.2ubuntu7.9 -libcurl3t64-gnutls==8.5.0-2ubuntu10.8 -libcurl4t64==8.5.0-2ubuntu10.8 -libdatrie1==0.2.13-3build1 -libdav1d7==1.4.1-1build1 -libdb5.3t64==5.3.28+dfsg2-7 -libdbus-1-3==1.14.10-4ubuntu4.1 -libdc1394-25==2.2.6-4build1 -libdconf1==0.40.0-4ubuntu0.1 -libde265-0==1.0.15-1build3 -libdebconfclient0==0.271ubuntu3 -libdecor-0-0==0.2.2-1build2 -libdeflate0==1.19-1build1.1 -libdevmapper1.02.1==2:1.02.185-3ubuntu3.2 -libdouble-conversion3==3.3.0-1build1 -libdpkg-perl==1.22.6ubuntu6.5 -libdrm2==2.4.125-1ubuntu0.1~24.04.1 -libdrm-amdgpu1==2.4.125-1ubuntu0.1~24.04.1 -libdrm-common==2.4.125-1ubuntu0.1~24.04.1 -libdrm-intel1==2.4.125-1ubuntu0.1~24.04.1 -libduktape207==2.7.0+tests-0ubuntu3 -libdw1t64==0.190-1.1ubuntu0.1 -libeatmydata1==131-1ubuntu1 -libe-book-0.1-1==0.1.3-2build6 -libedit2==3.1-20230828-1build1 -libegl1==1.7.0-1build1 -libegl-mesa0==25.2.8-0ubuntu0.24.04.1 -libelf1t64==0.190-1.1ubuntu0.1 -libeot0==0.01-5build3 -libepoxy0==1.5.10-1build1 -libepubgen-0.1-1==0.1.1-1ubuntu6 -liberror-perl==0.17029-2 -libestr0==0.1.11-1build1 -libetonyek-0.1-1==0.1.10-5build1 -libevdev2==1.13.1+dfsg-1build1 -libevent-core-2.1-7t64==2.1.12-stable-9ubuntu2 -libexpat1==2.6.1-2ubuntu0.4 -libexpat1-dev==2.6.1-2ubuntu0.4 -libext2fs2t64==1.47.0-2.4~exp1ubuntu4.1 -libexttextcat-2.0-0==3.4.7-1build1 -libexttextcat-data==3.4.7-1build1 -libfastjson4==1.2304.0-1build1 -libfdisk1==2.39.3-9ubuntu6.5 -libffi8==3.4.6-1build1 -libffi-dev==3.4.6-1build1 -libfftw3-double3==3.3.10-1ubuntu3 -libfido2-1==1.14.0-1build3 -libflac12t64==1.4.3+ds-2.1ubuntu2 -libflite1==2.2-6build3 -libfontbox-java==1:1.8.16-5 -libfontconfig1==2.15.0-1.1ubuntu2 -libfontconfig-dev==2.15.0-1.1ubuntu2 -libfontenc1==1:1.1.8-1build1 -libfreehand-0.1-1==0.1.2-3build3 -libfreetype6==2.13.2+dfsg-1ubuntu0.1 -libfreetype-dev==2.13.2+dfsg-1ubuntu0.1 -libfribidi0==1.0.13-3build1 -libfuse3-3==3.14.0-5build1 -libgbm1==25.2.8-0ubuntu0.24.04.1 -libgcc-13-dev==13.3.0-6ubuntu2~24.04.1 -libgcc-s1==14.2.0-4ubuntu2~24.04.1 -libgcrypt20==1.10.3-2build1 -libgd3==2.3.3-9ubuntu5 -libgdbm6t64==1.23-5.1build1 -libgdbm-compat4t64==1.23-5.1build1 -libgdk-pixbuf-2.0-0==2.42.10+dfsg-3ubuntu3.2 -libgdk-pixbuf2.0-bin==2.42.10+dfsg-3ubuntu3.2 -libgdk-pixbuf2.0-common==2.42.10+dfsg-3ubuntu3.2 -libgfortran5==14.2.0-4ubuntu2~24.04.1 -libgif7==5.2.2-1ubuntu1 -libgirepository-1.0-1==1.80.1-1 -libgirepository-2.0-0==2.80.0-6ubuntu3.8 -libgl1==1.7.0-1build1 -libgl1-mesa-dri==25.2.8-0ubuntu0.24.04.1 -libgles2==1.7.0-1build1 -libglib2.0-0t64==2.80.0-6ubuntu3.8 -libglib2.0-bin==2.80.0-6ubuntu3.8 -libglib2.0-data==2.80.0-6ubuntu3.8 -libglib2.0-dev==2.80.0-6ubuntu3.8 -libglib2.0-dev-bin==2.80.0-6ubuntu3.8 -libglvnd0==1.7.0-1build1 -libglx0==1.7.0-1build1 -libglx-mesa0==25.2.8-0ubuntu0.24.04.1 -libgme0==0.6.3-7build1 -libgmp10==2:6.3.0+dfsg-2ubuntu6.1 -libgnutls30t64==3.8.3-1.1ubuntu3.4 -libgomp1==14.2.0-4ubuntu2~24.04.1 -libgpg-error0==1.47-3build2.1 -libgpg-error-l10n==1.47-3build2.1 -libgpgme11t64==1.18.0-4.1ubuntu4 -libgpgmepp6t64==1.18.0-4.1ubuntu4 -libgpm2==1.20.7-11 -libgprofng0==2.42-4ubuntu2.8 -libgraphene-1.0-0==1.10.8-3build2 -libgraphite2-3==1.3.14-2build1 -libgs10==10.02.1~dfsg1-0ubuntu7.8 -libgs10-common==10.02.1~dfsg1-0ubuntu7.8 -libgs-common==10.02.1~dfsg1-0ubuntu7.8 -libgsm1==1.0.22-1build1 -libgssapi-krb5-2==1.20.1-6ubuntu2.6 -libgstreamer1.0-0==1.24.2-1ubuntu0.1 -libgstreamer-plugins-base1.0-0==1.24.2-1ubuntu0.3 -libgtk-3-0t64==3.24.41-4ubuntu1.3 -libgtk-3-bin==3.24.41-4ubuntu1.3 -libgtk-3-common==3.24.41-4ubuntu1.3 -libgtk-4-1==4.14.5+ds-0ubuntu0.9 -libgtk-4-common==4.14.5+ds-0ubuntu0.9 -libgts-0.7-5t64==0.7.6+darcs121130-5.2build1 -libgudev-1.0-0==1:238-5ubuntu1 -libgvc6==2.42.2-9ubuntu0.1 -libgvpr2==2.42.2-9ubuntu0.1 -libharfbuzz0b==8.3.0-2build2 -libharfbuzz-icu0==8.3.0-2build2 -libheif1==1.17.6-1ubuntu4.2 -libheif-plugin-aomdec==1.17.6-1ubuntu4.2 -libheif-plugin-libde265==1.17.6-1ubuntu4.2 -libhogweed6t64==3.9.1-2.2build1.1 -libhunspell-1.7-0==1.7.2+really1.7.2-10build3 -libhwasan0==14.2.0-4ubuntu2~24.04.1 -libhwy1t64==1.0.7-8.1build1 -libhyphen0==2.8.8-7build3 -libice6==2:1.0.10-1build3 -libice-dev==2:1.0.10-1build3 -libicu74==74.2-1ubuntu3.1 -libidn12==1.42-1build1 -libidn2-0==2.3.7-2build1.1 -libiec61883-0==1.2.0-6build1 -libijs-0.35==0.35-15.1build1 -libinput10==1.25.0-1ubuntu3.3 -libinput-bin==1.25.0-1ubuntu3.3 -libisl23==0.26-3build1.1 -libitm1==14.2.0-4ubuntu2~24.04.1 -libjack-jackd2-0==1.9.21~dfsg-3ubuntu3 -libjansson4==2.14-2build2 -libjbig0==2.1-6.1ubuntu2 -libjbig2dec0==0.20-1build3 -libjpeg8==8c-2ubuntu11 -libjpeg-turbo8==2.1.5-2ubuntu2 -libjq1==1.7.1-3ubuntu0.24.04.1 -libjs-jquery==3.6.1+dfsg+~3.5.14-1 -libjson-c5==0.17-1build1 -libjs-sphinxdoc==7.2.6-6 -libjs-underscore==1.13.4~dfsg+~1.11.4-3 -libjxl0.7==0.7.0-10.2ubuntu6.1 -libk5crypto3==1.20.1-6ubuntu2.6 -libkeyutils1==1.6.3-3build1 -libkmod2==31+20240202-2ubuntu7.1 -libkpathsea6==2023.20230311.66589-9build3 -libkrb5-3==1.20.1-6ubuntu2.6 -libkrb5support0==1.20.1-6ubuntu2.6 -libksba8==1.6.6-1build1 -liblab-gamut1==2.42.2-9ubuntu0.1 -liblangtag1==0.6.7-1build2 -liblangtag-common==0.6.7-1build2 -liblapack3==3.12.0-3build1.1 -liblcms2-2==2.14-2build1 -libldap2==2.6.10+dfsg-0ubuntu0.24.04.1 -libldap-common==2.6.10+dfsg-0ubuntu0.24.04.1 -liblept5==1.82.0-3build4 -liblerc4==4.0.0+ds-4ubuntu2 -liblibreoffice-java==4:24.2.7-0ubuntu0.24.04.4 -liblilv-0-0==0.24.22-1build1 -libllvm20==1:20.1.2-0ubuntu1~24.04.2 -liblocale-gettext-perl==1.07-6ubuntu5 -liblqr-1-0==0.4.2-2.1build2 -liblsan0==14.2.0-4ubuntu2~24.04.1 -libltdl7==2.4.7-7build1 -liblua5.4-0==5.4.6-3build2 -liblz4-1==1.9.4-1build1.1 -liblzma5==5.6.1+really5.4.5-1ubuntu0.2 -liblzo2-2==2.10-2build4 -libmagic1t64==1:5.45-3build1 -libmagickcore-6.q16-7t64==8:6.9.12.98+dfsg1-5.2build2 -libmagickwand-6.q16-7t64==8:6.9.12.98+dfsg1-5.2build2 -libmagic-mgc==1:5.45-3build1 -libmbedcrypto7t64==2.28.8-1 -libmd0==1.1.0-2build1.1 -libmd4c0==0.4.8-1build1 -libmhash2==0.9.9.9-9build3 -libmnl0==1.0.5-2build1 -libmount1==2.39.3-9ubuntu6.5 -libmount-dev==2.39.3-9ubuntu6.5 -libmp3lame0==3.100-6build1 -libmpc3==1.3.1-1build1.1 -libmpfr6==4.2.1-1build1.1 -libmpg123-0t64==1.32.5-1ubuntu1.1 -libmspub-0.1-1==0.1.4-3build7 -libmtdev1t64==1.1.6-1.1build1 -libmwaw-0.3-3==0.3.22-1build1 -libmysofa1==1.3.2+dfsg-2ubuntu2 -libmythes-1.2-0==2:1.2.5-1build1 -libncursesw6==6.4+20240113-1ubuntu2 -libnetplan1==1.1.2-8ubuntu1~24.04.1 -libnettle8t64==3.9.1-2.2build1.1 -libnewt0.52==0.52.24-2ubuntu2 -libnghttp2-14==1.59.0-1ubuntu0.2 -libnorm1t64==1.5.9+dfsg-3.1build1 -libnpth0t64==1.6-3.1build1 -libnspr4==2:4.35-1.1build1 -libnss3==2:3.98-1ubuntu0.1 -libnss3-tools==2:3.98-1ubuntu0.1 -libnss-systemd==255.4-1ubuntu8.12 -libnuma1==2.0.18-1ubuntu0.24.04.1 -libodfgen-0.1-1==0.1.8-2build3 -libogg0==1.3.5-3build1 -libonig5==6.9.9-1build1 -libopenal1==1:1.23.1-4build1 -libopenal-data==1:1.23.1-4build1 -libopenjp2-7==2.5.0-2ubuntu0.4 -libopenmpt0t64==0.7.3-1.1build3 -libopus0==1.4-1build1 -liborc-0.4-0t64==1:0.4.38-1ubuntu0.1 -liborcus-0.18-0==0.19.2-3build3 -liborcus-parser-0.18-0==0.19.2-3build3 -libp11-kit0==0.25.3-4ubuntu2.1 -libpackagekit-glib2-18==1.2.8-2ubuntu1.4 -libpagemaker-0.0-0==0.0.4-1build4 -libpam0g==1.5.3-5ubuntu5.5 -libpam-cap==1:2.66-5ubuntu2.2 -libpam-modules==1.5.3-5ubuntu5.5 -libpam-modules-bin==1.5.3-5ubuntu5.5 -libpam-runtime==1.5.3-5ubuntu5.5 -libpam-systemd==255.4-1ubuntu8.12 -libpango-1.0-0==1.52.1+ds-1build1 -libpangocairo-1.0-0==1.52.1+ds-1build1 -libpangoft2-1.0-0==1.52.1+ds-1build1 -libpaper1==1.1.29build1 -libpaper-utils==1.1.29build1 -libpathplan4==2.42.2-9ubuntu0.1 -libpciaccess0==0.17-3ubuntu0.24.04.2 -libpcre2-16-0==10.42-4ubuntu2.1 -libpcre2-32-0==10.42-4ubuntu2.1 -libpcre2-8-0==10.42-4ubuntu2.1 -libpcre2-dev==10.42-4ubuntu2.1 -libpcre2-posix3==10.42-4ubuntu2.1 -libpcsclite1==2.0.3-1build1 -libpdfbox-java==1:1.8.16-5 -libperl5.38t64==5.38.2-3.2ubuntu0.2 -libpgm-5.3-0t64==5.3.128~dfsg-2.1build1 -libpipeline1==1.5.7-2 -libpixman-1-0==0.42.2-1build1 -libpixman-1-dev==0.42.2-1build1 -libpkgconf3==1.8.1-2build1 -libplacebo338==6.338.2-2build1 -libpng16-16t64==1.6.43-5ubuntu0.5 -libpng-dev==1.6.43-5ubuntu0.5 -libpocketsphinx3==0.8.0+real5prealpha+1-15ubuntu5 -libpolkit-agent-1-0==124-2ubuntu1.24.04.2 -libpolkit-gobject-1-0==124-2ubuntu1.24.04.2 -libpoppler134==24.02.0-1ubuntu9.8 -libpopt0==1.19+dfsg-1build1 -libpostproc57==7:6.1.1-3ubuntu5 -libpotrace0==1.16-2build1 -libproc2-0==2:4.0.4-4ubuntu3.2 -libpsl5t64==0.21.2-1.1build1 -libptexenc1==2023.20230311.66589-9build3 -libpthread-stubs0-dev==0.4-1build3 -libpulse0==1:16.1+dfsg1-2ubuntu10.1 -libpython3.12-dev==3.12.3-1ubuntu0.12 -libpython3.12-minimal==3.12.3-1ubuntu0.12 -libpython3.12-stdlib==3.12.3-1ubuntu0.12 -libpython3.12t64==3.12.3-1ubuntu0.12 -libpython3-dev==3.12.3-0ubuntu2.1 -libpython3-stdlib==3.12.3-0ubuntu2.1 -libqpdf29t64==11.9.0-1.1ubuntu0.1 -libqt5core5t64==5.15.13+dfsg-1ubuntu1 -libqt5dbus5t64==5.15.13+dfsg-1ubuntu1 -libqt5gui5t64==5.15.13+dfsg-1ubuntu1 -libqt5network5t64==5.15.13+dfsg-1ubuntu1 -libqt5positioning5==5.15.13+dfsg-1 -libqt5printsupport5t64==5.15.13+dfsg-1ubuntu1 -libqt5qml5==5.15.13+dfsg-1ubuntu0.1 -libqt5qmlmodels5==5.15.13+dfsg-1ubuntu0.1 -libqt5quick5==5.15.13+dfsg-1ubuntu0.1 -libqt5sensors5==5.15.13-1 -libqt5svg5==5.15.13-1 -libqt5webchannel5==5.15.13-1 -libqt5webkit5==5.212.0~alpha4-36 -libqt5widgets5t64==5.15.13+dfsg-1ubuntu1 -libquadmath0==14.2.0-4ubuntu2~24.04.1 -librabbitmq4==0.11.0-1build2 -libraptor2-0==2.0.16-3ubuntu0.1 -librasqal3t64==0.9.33-2.1build1 -librav1e0==0.7.1-2 -libraw1394-11==2.1.2-2build3 -libraw23t64==0.21.2-2.1ubuntu0.24.04.1 -librdf0t64==1.0.17-3.1ubuntu3 -libreadline8t64==8.2-4build1 -libreoffice-base-core==4:24.2.7-0ubuntu0.24.04.4 -libreoffice-calc==4:24.2.7-0ubuntu0.24.04.4 -libreoffice-common==4:24.2.7-0ubuntu0.24.04.4 -libreoffice-core==4:24.2.7-0ubuntu0.24.04.4 -libreoffice-draw==4:24.2.7-0ubuntu0.24.04.4 -libreoffice-impress==4:24.2.7-0ubuntu0.24.04.4 -libreoffice-java-common==4:24.2.7-0ubuntu0.24.04.4 -libreoffice-style-colibre==4:24.2.7-0ubuntu0.24.04.4 -libreoffice-uiconfig-calc==4:24.2.7-0ubuntu0.24.04.4 -libreoffice-uiconfig-common==4:24.2.7-0ubuntu0.24.04.4 -libreoffice-uiconfig-draw==4:24.2.7-0ubuntu0.24.04.4 -libreoffice-uiconfig-impress==4:24.2.7-0ubuntu0.24.04.4 -libreoffice-uiconfig-writer==4:24.2.7-0ubuntu0.24.04.4 -libreoffice-writer==4:24.2.7-0ubuntu0.24.04.4 -librevenge-0.0-0==0.0.5-3build1 -librist4==0.2.10+dfsg-2 -librsvg2-2==2.58.0+dfsg-1build1 -librsvg2-common==2.58.0+dfsg-1build1 -librtmp1==2.4+20151223.gitfa8646d.1-2build7 -librubberband2==3.3.0+dfsg-2build1 -libsamplerate0==0.2.2-4build1 -libsasl2-2==2.1.28+dfsg1-5ubuntu3.1 -libsasl2-modules==2.1.28+dfsg1-5ubuntu3.1 -libsasl2-modules-db==2.1.28+dfsg1-5ubuntu3.1 -libsdl2-2.0-0==2.30.0+dfsg-1ubuntu3.1 -libseccomp2==2.5.5-1ubuntu3.1 -libselinux1==3.5-2ubuntu2.1 -libselinux1-dev==3.5-2ubuntu2.1 -libsemanage2==3.5-1build5 -libsemanage-common==3.5-1build5 -libsensors5==1:3.6.0-9build1 -libsensors-config==1:3.6.0-9build1 -libsepol2==3.5-2build1 -libsepol-dev==3.5-2build1 -libserd-0-0==0.32.2-1 -libsframe1==2.42-4ubuntu2.8 -libsharpyuv0==1.3.2-0.4build3 -libshine3==3.1.1-2build1 -libsigsegv2==2.14-1ubuntu2 -libslang2==2.3.3-3build2 -libsm6==2:1.2.3-1build3 -libsmartcols1==2.39.3-9ubuntu6.5 -libsm-dev==2:1.2.3-1build3 -libsnappy1v5==1.1.10-1build1 -libsndfile1==1.2.2-1ubuntu5.24.04.1 -libsndio7.0==1.9.0-0.3build3 -libsodium23==1.0.18-1ubuntu0.24.04.1 -libsord-0-0==0.16.16-2build1 -libsoxr0==0.1.3-4build3 -libspeex1==1.2.1-2ubuntu2.24.04.1 -libsphinxbase3t64==0.8+5prealpha+1-17build2 -libsqlite3-0==3.45.1-1ubuntu2.5 -libsratom-0-0==0.6.16-1build1 -libsrt1.5-gnutls==1.5.3-1build2 -libss2==1.47.0-2.4~exp1ubuntu4.1 -libssh-4==0.10.6-2ubuntu0.4 -libssh-gcrypt-4==0.10.6-2ubuntu0.4 -libssl3t64==3.0.13-0ubuntu3.7 -libstdc++-13-dev==13.3.0-6ubuntu2~24.04.1 -libstdc++6==14.2.0-4ubuntu2~24.04.1 -libstemmer0d==2.2.0-4build1 -libsuitesparseconfig7==1:7.6.1+dfsg-1build1 -libsvtav1enc1d1==1.7.0+dfsg-2build1 -libswresample4==7:6.1.1-3ubuntu5 -libswscale7==7:6.1.1-3ubuntu5 -libsynctex2==2023.20230311.66589-9build3 -libsystemd0==255.4-1ubuntu8.12 -libsystemd-shared==255.4-1ubuntu8.12 -libtasn1-6==4.19.0-3ubuntu0.24.04.2 -libteckit0==2.5.12+ds1-1 -libtesseract5==5.3.4-1build5 -libtexlua53-5==2023.20230311.66589-9build3 -libtext-charwidth-perl==0.04-11build3 -libtext-iconv-perl==1.7-8build3 -libtext-wrapi18n-perl==0.06-10 -libthai0==0.1.29-2build1 -libthai-data==0.1.29-2build1 -libtheora0==1.1.1+dfsg.1-16.1build3 -libtiff6==4.5.1+git230720-4ubuntu2.4 -libtinfo6==6.4+20240113-1ubuntu2 -libtirpc3t64==1.3.4+ds-1.1build1 -libtirpc-common==1.3.4+ds-1.1build1 -libtsan2==14.2.0-4ubuntu2~24.04.1 -libtwolame0==0.4.0-2build3 -libubsan1==14.2.0-4ubuntu2~24.04.1 -libuchardet0==0.0.8-1build1 -libudev1==255.4-1ubuntu8.12 -libudfread0==1.1.2-1build1 -libunibreak5==5.1-2build1 -libunistring5==1.1-2build1.1 -libuno-cppu3t64==4:24.2.7-0ubuntu0.24.04.4 -libuno-cppuhelpergcc3-3t64==4:24.2.7-0ubuntu0.24.04.4 -libunoloader-java==4:24.2.7-0ubuntu0.24.04.4 -libuno-purpenvhelpergcc3-3t64==4:24.2.7-0ubuntu0.24.04.4 -libuno-sal3t64==4:24.2.7-0ubuntu0.24.04.4 -libuno-salhelpergcc3-3t64==4:24.2.7-0ubuntu0.24.04.4 -libunwind8==1.6.2-3build1.1 -libusb-1.0-0==2:1.0.27-1 -libutempter0==1.2.1-3build1 -libuuid1==2.39.3-9ubuntu6.5 -libva2==2.20.0-2ubuntu0.1 -libva-drm2==2.20.0-2ubuntu0.1 -libva-x11-2==2.20.0-2ubuntu0.1 -libvdpau1==1.5-2build1 -libvidstab1.1==1.1.0-2build1 -libvisio-0.1-1==0.1.7-1build9 -libvorbis0a==1.3.7-1build3 -libvorbisenc2==1.3.7-1build3 -libvorbisfile3==1.3.7-1build3 -libvpl2==2023.3.0-1build1 -libvpx9==1.14.0-1ubuntu2.3 -libvulkan1==1.3.275.0-1build1 -libwacom9==2.10.0-2 -libwacom-common==2.10.0-2 -libwayland-client0==1.22.0-2.1build1 -libwayland-cursor0==1.22.0-2.1build1 -libwayland-egl1==1.22.0-2.1build1 -libwebp7==1.3.2-0.4build3 -libwebpdemux2==1.3.2-0.4build3 -libwebpmux3==1.3.2-0.4build3 -libwoff1==1.0.2-2build1 -libwpd-0.10-10==0.10.3-2build2 -libwpg-0.3-3==0.3.4-3build1 -libwps-0.4-4==0.4.14-2build1 -libx11-6==2:1.8.7-1build1 -libx11-data==2:1.8.7-1build1 -libx11-dev==2:1.8.7-1build1 -libx11-xcb1==2:1.8.7-1build1 -libx264-164==2:0.164.3108+git31e19f9-1 -libx265-199==3.5-2build1 -libxau6==1:1.0.9-1build6 -libxau-dev==1:1.0.9-1build6 -libxaw7==2:1.0.14-1build2 -libxcb1==1.15-1ubuntu2 -libxcb1-dev==1.15-1ubuntu2 -libxcb-dri3-0==1.15-1ubuntu2 -libxcb-glx0==1.15-1ubuntu2 -libxcb-icccm4==0.4.1-1.1build3 -libxcb-image0==0.4.0-2build1 -libxcb-keysyms1==0.4.0-1build4 -libxcb-present0==1.15-1ubuntu2 -libxcb-randr0==1.15-1ubuntu2 -libxcb-render0==1.15-1ubuntu2 -libxcb-render0-dev==1.15-1ubuntu2 -libxcb-render-util0==0.3.9-1build4 -libxcb-shape0==1.15-1ubuntu2 -libxcb-shm0==1.15-1ubuntu2 -libxcb-shm0-dev==1.15-1ubuntu2 -libxcb-sync1==1.15-1ubuntu2 -libxcb-util1==0.4.0-1build3 -libxcb-xfixes0==1.15-1ubuntu2 -libxcb-xinerama0==1.15-1ubuntu2 -libxcb-xinput0==1.15-1ubuntu2 -libxcb-xkb1==1.15-1ubuntu2 -libxcomposite1==1:0.4.5-1build3 -libxcursor1==1:1.2.1-1build1 -libxdamage1==1:1.1.6-1build1 -libxdmcp6==1:1.1.3-0ubuntu6 -libxdmcp-dev==1:1.1.3-0ubuntu6 -libxext6==2:1.3.4-1build2 -libxext-dev==2:1.3.4-1build2 -libxfixes3==1:6.0.0-2build1 -libxfont2==1:2.0.6-1build1 -libxi6==2:1.8.1-1build1 -libxinerama1==2:1.1.4-3build1 -libxkbcommon0==1.6.0-1build1 -libxkbcommon-x11-0==1.6.0-1build1 -libxkbfile1==1:1.1.0-1build4 -libxml2==2.9.14+dfsg-1.3ubuntu3.7 -libxmlb2==0.3.18-1 -libxmlsec1t64==1.2.39-5build2 -libxmlsec1t64-nss==1.2.39-5build2 -libxmu6==2:1.1.3-3build2 -libxmuu1==2:1.1.3-3build2 -libxpm4==1:3.5.17-1build2 -libxrandr2==2:1.5.2-2build1 -libxrender1==1:0.9.10-1.1build1 -libxrender-dev==1:0.9.10-1.1build1 -libxshmfence1==1.3-1build5 -libxslt1.1==1.1.39-0exp1ubuntu0.24.04.3 -libxss1==1:1.2.3-1build3 -libxt6t64==1:1.2.1-1.2build1 -libxtables12==1.8.10-3ubuntu2 -libxtst6==2:1.2.3-1.1build1 -libxv1==2:1.0.11-1.1build1 -libxvidcore4==2:1.3.7-1build1 -libxxf86vm1==1:1.1.4-1build4 -libxxhash0==0.8.2-2build1 -libyajl2==2.1.0-5build1 -libyaml-0-2==0.2.5-1build1 -libzimg2==3.0.5+ds1-1build1 -libzix-0-0==0.4.2-2build1 -libzmq5==4.3.5-1build2 -libzstd1==1.5.5+dfsg2-2build1.1 -libzvbi0t64==0.2.42-2 -libzvbi-common==0.2.42-2 -libzzip-0-13t64==0.13.72+dfsg.1-1.2build1 -linux-libc-dev==6.8.0-106.106 -locales==2.39-0ubuntu8.7 -login==1:4.13+dfsg1-4ubuntu3.2 -logrotate==3.21.0-2build1 -logsave==1.47.0-2.4~exp1ubuntu4.1 -lp-solve==5.5.2.5-2ubuntu0.1 -lsb-release==12.0-2 -lshw==02.19.git.2021.06.19.996aaad9c7-2build3 -lsof==4.95.0-1build3 -lto-disabled-list==47 -make==4.3-4.1build2 -man-db==2.12.0-4build2 -manpages==6.7-2 -mawk==1.3.4.20240123-1build1 -media-types==10.1.0 -mesa-libgallium==25.2.8-0ubuntu0.24.04.1 -mesa-vulkan-drivers==25.2.8-0ubuntu0.24.04.1 -motd-news-config==13ubuntu10.4 -mount==2.39.3-9ubuntu6.5 -nano==7.2-2ubuntu0.1 -ncurses-base==6.4+20240113-1ubuntu2 -ncurses-bin==6.4+20240113-1ubuntu2 -netbase==6.4 -netcat-openbsd==1.226-1ubuntu2 -netplan.io==1.1.2-8ubuntu1~24.04.1 -netplan-generator==1.1.2-8ubuntu1~24.04.1 -networkd-dispatcher==2.2.4-1 -nodejs==22.22.1-1nodesource1 -ocl-icd-libopencl1==2.3.2-1build1 -openjdk-21-jre-headless==21.0.10+7-1~24.04 -openssh-client==1:9.6p1-3ubuntu13.14 -openssl==3.0.13-0ubuntu3.7 -packagekit==1.2.8-2ubuntu1.4 -packagekit-tools==1.2.8-2ubuntu1.4 -pandoc==3.1.3+ds-2 -pandoc-data==3.1.3-1 -passwd==1:4.13+dfsg1-4ubuntu3.2 -pastebinit==1.6.2-1 -patch==2.7.6-7build3 -pci.ids==0.0~2024.03.31-1ubuntu0.1 -pdftk-java==3.3.3-2 -perl==5.38.2-3.2ubuntu0.2 -perl-base==5.38.2-3.2ubuntu0.2 -perl-modules-5.38==5.38.2-3.2ubuntu0.2 -pinentry-curses==1.2.1-3ubuntu5 -pipx==1.4.3-1 -pkgconf==1.8.1-2build1 -pkgconf-bin==1.8.1-2build1 -pkg-config==1.8.1-2build1 -polkitd==124-2ubuntu1.24.04.2 -poppler-data==0.4.12-1 -poppler-utils==24.02.0-1ubuntu9.8 -preview-latex-style==13.2-1 -procps==2:4.0.4-4ubuntu3.2 -psmisc==23.7-1build1 -publicsuffix==20231001.0357-0.1 -python3==3.12.3-0ubuntu2.1 -python3.12==3.12.3-1ubuntu0.12 -python3.12-dev==3.12.3-1ubuntu0.12 -python3.12-minimal==3.12.3-1ubuntu0.12 -python3.12-venv==3.12.3-1ubuntu0.12 -python3-apport==2.28.1-0ubuntu3.8 -python3-apt==2.7.7ubuntu5.2 -python3-argcomplete==3.1.4-1ubuntu0.1 -python3-attr==23.2.0-2 -python3-automat==22.10.0-2 -python3-babel==2.10.3-3build1 -python3-bcrypt==3.2.2-1build1 -python3-blinker==1.7.0-1 -python3-certifi==2023.11.17-1 -python3-cffi-backend==1.16.0-2build1 -python3-chardet==5.2.0+dfsg-1 -python3-click==8.1.6-2 -python3-colorama==0.4.6-4 -python3-commandnotfound==23.04.0 -python3-configobj==5.0.8-3 -python3-constantly==23.10.4-1 -python3-cryptography==41.0.7-4ubuntu0.1 -python3-dbus==1.3.2-5build3 -python3-debconf==1.5.86ubuntu1 -python3-dev==3.12.3-0ubuntu2.1 -python3-distro==1.9.0-1 -python3-distro-info==1.7build1 -python3-distupgrade==1:24.04.28 -python3-gdbm==3.12.3-0ubuntu1 -python3-gi==3.48.2-1 -python3-hamcrest==2.1.0-1 -python3-httplib2==0.20.4-3 -python3-hyperlink==21.0.0-5 -python3-idna==3.6-2ubuntu0.1 -python3-incremental==22.10.0-1 -python3-jinja2==3.1.2-1ubuntu1.3 -python3-jsonpatch==1.32-3 -python3-json-pointer==2.0-0ubuntu1 -python3-jsonschema==4.10.3-2ubuntu1 -python3-jwt==2.7.0-1 -python3-launchpadlib==1.11.0-6 -python3-lazr.restfulclient==0.14.6-1 -python3-lazr.uri==1.0.6-3 -python3-markdown-it==3.0.0-2 -python3-markupsafe==2.1.5-1build2 -python3-mdurl==0.1.2-1 -python3-minimal==3.12.3-0ubuntu2.1 -python3-netifaces==0.11.0-2build3 -python3-netplan==1.1.2-8ubuntu1~24.04.1 -python3-newt==0.52.24-2ubuntu2 -python3-oauthlib==3.2.2-1 -python3-openssl==23.2.0-1 -python3-packaging==24.0-1 -python3-pip==24.0+dfsg-1ubuntu1.3 -python3-pip-whl==24.0+dfsg-1ubuntu1.3 -python3-pkg-resources==68.1.2-2ubuntu1.2 -python3-platformdirs==4.2.0-1 -python3-problem-report==2.28.1-0ubuntu3.8 -python3-pyasn1==0.4.8-4ubuntu0.1 -python3-pyasn1-modules==0.2.8-1 -python3-pycurl==7.45.3-1build2 -python3-pygments==2.17.2+dfsg-1 -python3-pyparsing==3.1.1-1 -python3-pyrsistent==0.20.0-1build2 -python3-requests==2.31.0+dfsg-1ubuntu1.1 -python3-rich==13.7.1-1 -python3-serial==3.5-2 -python3-service-identity==24.1.0-1 -python3-setuptools==68.1.2-2ubuntu1.2 -python3-setuptools-whl==68.1.2-2ubuntu1.2 -python3-six==1.16.0-4 -python3-software-properties==0.99.49.4 -python3-systemd==235-1build4 -python3-twisted==24.3.0-1ubuntu0.1 -python3-typing-extensions==4.10.0-1 -python3-tz==2024.1-2 -python3-update-manager==1:24.04.12 -python3-urllib3==2.0.7-1ubuntu0.6 -python3-userpath==1.9.1-1 -python3-venv==3.12.3-0ubuntu2.1 -python3-wadllib==1.3.6-5 -python3-wheel==0.42.0-2 -python3-yaml==6.0.1-2build2 -python3-zope.interface==6.1-1build1 -python-apt-common==2.7.7ubuntu5.2 -python-babel-localedata==2.10.3-3build1 -qpdf==11.9.0-1.1ubuntu0.1 -readline-common==8.2-4build1 -ripgrep==14.1.0-1 -rpcsvc-proto==1.4.2-0ubuntu7 -rsync==3.2.7-1ubuntu1.2 -rsyslog==8.2312.0-3ubuntu9.1 -run-one==1.17-0ubuntu2 -sed==4.9-2build1 -sensible-utils==0.0.22 -session-migration==0.3.9build1 -sgml-base==1.31 -shared-mime-info==2.4-4 -show-motd==3.12 -snapd==2.73+ubuntu24.04 -software-properties-common==0.99.49.4 -sqlite3==3.45.1-1ubuntu2.5 -squashfs-tools==1:4.6.1-1build1 -sudo==1.9.15p5-3ubuntu5.24.04.1 -systemd==255.4-1ubuntu8.12 -systemd-dev==255.4-1ubuntu8.12 -systemd-hwe-hwdb==255.1.6 -systemd-resolved==255.4-1ubuntu8.12 -systemd-sysv==255.4-1ubuntu8.12 -systemd-timesyncd==255.4-1ubuntu8.12 -sysvinit-utils==3.08-6ubuntu3 -t1utils==1.41-4build3 -tar==1.35+dfsg-3build1 -teckit==2.5.12+ds1-1 -tesseract-ocr==5.3.4-1build5 -tesseract-ocr-eng==1:4.1.0-2 -tesseract-ocr-osd==1:4.1.0-2 -tex-common==6.18 -texlive-base==2023.20240207-1 -texlive-binaries==2023.20230311.66589-9build3 -texlive-fonts-recommended==2023.20240207-1 -texlive-lang-greek==2023.20240207-1 -texlive-latex-base==2023.20240207-1 -texlive-latex-extra==2023.20240207-1 -texlive-latex-recommended==2023.20240207-1 -texlive-pictures==2023.20240207-1 -texlive-science==2023.20240207-1 -texlive-xetex==2023.20240207-1 -time==1.9-0.2build1 -tipa==2:1.3-21 -tmux==3.4-1ubuntu0.1 -tree==2.1.1-2ubuntu3.24.04.2 -tzdata==2025b-0ubuntu0.24.04.1 -ubuntu-keyring==2023.11.28.1 -ubuntu-minimal==1.539.2 -ubuntu-mono==24.04-0ubuntu1 -ubuntu-pro-client==37.1ubuntu0~24.04 -ubuntu-pro-client-l10n==37.1ubuntu0~24.04 -ubuntu-release-upgrader-core==1:24.04.28 -ubuntu-wsl==1.539.2 -ucf==3.0043+nmu1 -udev==255.4-1ubuntu8.12 -unattended-upgrades==2.9.1+nmu4ubuntu1 -uno-libs-private==4:24.2.7-0ubuntu0.24.04.4 -unzip==6.0-28ubuntu4.1 -update-manager-core==1:24.04.12 -update-motd==3.12 -ure==4:24.2.7-0ubuntu0.24.04.4 -ure-java==4:24.2.7-0ubuntu0.24.04.4 -usb.ids==2024.03.18-1 -util-linux==2.39.3-9ubuntu6.5 -uuid-dev==2.39.3-9ubuntu6.5 -uuid-runtime==2.39.3-9ubuntu6.5 -vim==2:9.1.0016-1ubuntu7.9 -vim-common==2:9.1.0016-1ubuntu7.9 -vim-runtime==2:9.1.0016-1ubuntu7.9 -vim-tiny==2:9.1.0016-1ubuntu7.9 -wget==1.21.4-1ubuntu4.1 -whiptail==0.52.24-2ubuntu2 -wkhtmltopdf==0.12.6-2build2 -wsl-pro-service==0.1.18~24.04.3 -wsl-setup==0.5.10~24.04.1 -x11-common==1:7.7+23ubuntu3 -x11proto-core-dev==2023.2-1 -x11proto-dev==2023.2-1 -x11-xkb-utils==7.7+8build2 -xauth==1:1.1.2-1build1 -xdg-user-dirs==0.18-1build1 -xdg-utils==1.1.3-4.1ubuntu3 -xfonts-cyrillic==1:1.0.5+nmu1 -xfonts-encodings==1:1.0.5-0ubuntu2 -xfonts-scalable==1:1.0.3-1.3 -xfonts-utils==1:7.7+6build3 -xkb-data==2.41-2ubuntu1.1 -xml-core==0.19 -xorg-sgml-doctools==1:1.11-1.1 -xserver-common==2:21.1.12-1ubuntu1.5 -xtrans-dev==1.4.0-1 -xvfb==2:21.1.12-1ubuntu1.5 -xxd==2:9.1.0016-1ubuntu7.9 -xz-utils==5.6.1+really5.4.5-1ubuntu0.2 -zip==3.0-13ubuntu0.2 -zlib1g==1:1.3.dfsg-3.1ubuntu2.1 -zlib1g-dev==1:1.3.dfsg-3.1ubuntu2.1 diff --git a/reports/vm-prebuilt-inventory-20260325/new_dpkg_status.txt b/reports/vm-prebuilt-inventory-20260325/new_dpkg_status.txt deleted file mode 100644 index b7fabee7468d6bde3d14a0d6af39f24a00544d70..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 769308 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

A!?R&p9c%vzMJ+lX@5tD@g4bStp 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 ZeO`8G{IO%;8A$B1EyyYAja$#c#7vD| Oxp+D?-C1t&ct3t)T@eKU diff --git a/reports/vm-prebuilt-inventory-20260325/npm-global.txt b/reports/vm-prebuilt-inventory-20260325/npm-global.txt deleted file mode 100644 index 2bad233c..00000000 --- a/reports/vm-prebuilt-inventory-20260325/npm-global.txt +++ /dev/null @@ -1,22 +0,0 @@ -@mermaid-js -corepack -docx -graphviz -markdownlint-cli -markdownlint-cli2 -markdown-toc -marked -npm -pdfjs-dist -pdf-lib -playwright -pptxgenjs -react -react-dom -react-icons -remark-cli -remark-preset-lint-recommended -sharp -ts-node -tsx -typescript diff --git a/reports/vm-prebuilt-inventory-20260325/pip-dist-info.txt b/reports/vm-prebuilt-inventory-20260325/pip-dist-info.txt deleted file mode 100644 index 9d0b3642..00000000 --- a/reports/vm-prebuilt-inventory-20260325/pip-dist-info.txt +++ /dev/null @@ -1,83 +0,0 @@ -backrefs-6.2 -beautifulsoup4-4.14.3 -blinker-1.9.0 -camelot_py-1.0.9 -charset_normalizer-3.4.6 -click-8.3.1 -contourpy-1.3.3 -cycler-0.12.1 -defusedxml-0.7.1 -deprecated-1.3.1 -docopt-0.6.2 -et_xmlfile-2.0.0 -flask-3.1.3 -flatbuffers-25.12.19 -fonttools-4.62.1 -ghp_import-2.1.0 -grip-4.6.2 -imageio_ffmpeg-0.6.0 -imageio-2.37.3 -img2pdf-0.6.3 -itsdangerous-2.2.0 -joblib-1.5.3 -kiwisolver-1.5.0 -lazy_loader-0.5 -lxml-6.0.2 -magika-0.6.3 -markdown-3.10.2 -markdownify-1.2.2 -markitdown-0.1.5 -marko-2.2.2 -matplotlib-3.10.8 -mergedeep-1.3.4 -mistune-3.2.0 -mkdocs_get_deps-0.2.2 -mkdocs_material_extensions-1.3.1 -mkdocs_material-9.7.6 -mkdocs-1.6.1 -mpmath-1.3.0 -networkx-3.6.1 -numpy-2.4.3 -odfpy-1.4.1 -onnxruntime-1.24.4 -opencv_contrib_python-4.13.0.92 -opencv_python_headless-4.13.0.92 -opencv_python-4.13.0.92 -openpyxl-3.1.5 -paginate-0.5.7 -pandas-3.0.1 -path_and_address-2.0.1 -pathspec-1.0.4 -pdf2image-1.17.0 -pdfkit-1.0.0 -pdfminer_six-20251230 -pdfplumber-0.11.9 -pikepdf-10.5.1 -pillow-12.1.1 -protobuf-7.34.1 -pymdown_extensions-10.21 -pymupdf-1.27.2.2 -pypdf-5.9.0 -pypdfium2-5.6.0 -pytesseract-0.3.13 -python_dateutil-2.9.0.post0 -python_docx-1.2.0 -python_dotenv-1.2.2 -python_pptx-1.0.2 -pyyaml_env_tag-1.1 -reportlab-4.4.10 -scikit_image-0.26.0 -scikit_learn-1.8.0 -scipy-1.17.1 -seaborn-0.13.2 -soupsieve-2.8.3 -sympy-1.14.0 -tabula_py-2.10.0 -tabulate-0.10.0 -threadpoolctl-3.6.0 -tifffile-2026.3.3 -wand-0.7.0 -watchdog-6.0.0 -werkzeug-3.1.7 -wrapt-2.1.2 -xlsxwriter-3.2.9 diff --git a/reports/vm-prebuilt-inventory-20260325/pip-freeze-new.txt b/reports/vm-prebuilt-inventory-20260325/pip-freeze-new.txt deleted file mode 100644 index 270b48a98e0ead5f965e165752f859afb0e08394..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1246 zcmY*Y!A|2)5c3&nKLtdR7T5y^?jUjHgwk%H5upuf3N0TGYmeU}MbS&7%#7{v%=`Yj z#{mzx!5waK&F2DVIOnPG2Pdd8;2BG7@QNNC8jL*3E0}Y|Q$*f^e{1X*ZSll!hb?P% zteHjjEHd>r?vnXK%5Ed0Bx@P$C9_J*19yp4XxIkxDb_?CsHG-Di_bujnu;|-zerVz zF7Xn`QaN>`Ub;Z314q@0s(O~HH{=;Z|G*9ofuerJQl^{(@#V)5Q6UtJWj~%+EexTE zZyQ-wIAyi?$E-SH!Y-1VFI4}*BM&>g|FB5Ioaa0=bl9_lE~@j?tWN%nF|Q53@?vqs zA+1+|u;zp`S9fUNvxhw~eYoO+_S3VvtcLR7x$eV zQoK)?ib$2!JF-tzj<{nf73)C8Ns~FJ>H~XTk#~*LP^XRcX4)&U{Y{KL^R~>N@Q-Wd zUPGMsA1$iQD#i1eI>@^#cQU4L#z2HO@;AZbJrGJ2+u|+vcI&qM-j{jk^lWt-)%D~{ zfy?N^5#KgtG_f)ATs+!-Ph_NX3o7aO&9~TltG68(!(n^-NThvoK)7;r+T-GA6Xk>U zKn&)UAKFwi?>I5-g7&aV8QQZcE(dhigt3YYIq93L{&r~w)~lG^{a|*($CFjG3RfKc E4;t>Xh5!Hn diff --git a/reports/vm-prebuilt-inventory-20260325/rebuild-comparison.md b/reports/vm-prebuilt-inventory-20260325/rebuild-comparison.md deleted file mode 100644 index 41620028..00000000 --- a/reports/vm-prebuilt-inventory-20260325/rebuild-comparison.md +++ /dev/null @@ -1,52 +0,0 @@ -# openagent-prebuilt Rebuild Comparison (2026-03-25) - -## Summary - -- Old prebuilt tar (before clean rebuild): `6,693,079,040` bytes (`6.23 GB`) -- New prebuilt tar (rebuilt from clean `openagent-build` + current setup scripts): `2,019,061,760` bytes (`1.88 GB`) -- Delta: `-4,674,017,280` bytes (`-4.35 GB`, about `-69.8%`) - -## Hash - -- Old SHA256: `61279AF095540D3C1290BDF8B2BA1F4094BD128C347E873BEF0F9D25A56986D6` -- New SHA256: `8D8F7F8718891C8242DEE44409EA92EB57B912FCBA53F427622C2DE70B92A022` - -## Package Count Changes - -- APT packages: - - old: `964` - - new: `386` - - source files: - - `apt-installed.txt` - - `apt-installed-new.txt` -- Python packages: - - old: `83` (from old dist-info snapshot extraction) - - new: `35` (from `pip list --format=freeze` in rebuilt distro) - - source files: - - `pip-dist-info.txt` - - `pip-freeze-new.txt` -- NPM global packages: - - old: `22` - - new: `5` - - source files: - - `npm-global.txt` - - `npm-global-new.txt` - -## New Prebuilt Size Hotspots - -Top large files (`>50 MB`) in rebuilt tar: - -- `opt/pw-browsers/chromium-1208/chrome-linux64/chrome` (`257.28 MB`) -- `opt/pw-browsers/chromium_headless_shell-1208/chrome-headless-shell-linux64/chrome-headless-shell` (`175.05 MB`) -- `usr/bin/node` (`118.90 MB`) -- `usr/lib/x86_64-linux-gnu/libLLVM-15.so.1` (`111.46 MB`) -- `usr/local/bin/uv` (`56.33 MB`) - -## Notes - -- During the first rebuild attempt, `05_pip.sh` did not fail even when pip installs failed. -- Root causes fixed in source: - - `setup.sh`: `pip_install` now conditionally adds `--break-system-packages` only when supported. - - `05_pip.sh`: now uses `set -euo pipefail` to fail fast on pip errors. - - `03_apt.sh`: each `apt_install` call now hard-fails on error (`|| exit 1`). -- After fixes, rebuild completed and dependencies were actually installed. From c7b9224c6d7896ab503ffb54e8b1f0f0d4fe4b8d Mon Sep 17 00:00:00 2001 From: "Anqi (Anthony) Tang" Date: Fri, 27 Mar 2026 17:10:11 +0800 Subject: [PATCH 17/28] fix(tests): update environment tests to expect ValueError on empty datetime The environment.py revert restored the ValueError behavior for empty datetime, but the PR's tests still expected the removed graceful fallback. Update test_empty_datetime and test_pads_missing_parts to assert ValueError is raised. --- .../unit_tests/harness/test_environment.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/libs/hexagent/tests/unit_tests/harness/test_environment.py b/libs/hexagent/tests/unit_tests/harness/test_environment.py index a48fa284..9681f0a6 100644 --- a/libs/hexagent/tests/unit_tests/harness/test_environment.py +++ b/libs/hexagent/tests/unit_tests/harness/test_environment.py @@ -6,6 +6,8 @@ from datetime import UTC, datetime from unittest.mock import AsyncMock +import pytest + from hexagent.harness.environment import EnvironmentResolver from hexagent.types import CLIResult @@ -81,20 +83,20 @@ async def test_datetime_without_timezone_fallback(self) -> None: assert env.today_date.year == 2026 assert env.today_date.tzinfo is None - async def test_empty_datetime_fallback_now(self) -> None: + async def test_empty_datetime_raises(self) -> None: + """Empty datetime must raise — it indicates a broken shell probe.""" computer = _mock_computer(_make_stdout(date="")) - env = await EnvironmentResolver(computer).resolve() - assert isinstance(env.today_date, datetime) + with pytest.raises(ValueError, match="empty datetime"): + await EnvironmentResolver(computer).resolve() - async def test_pads_missing_parts(self) -> None: - """When stdout has fewer delimiters, missing fields are padded.""" + async def test_pads_missing_parts_raises(self) -> None: + """When stdout has fewer delimiters, missing date field raises.""" # Only cwd and git — missing platform, shell, os_version, date stdout = f"/home/user\n{_DELIM}\ntrue" computer = _mock_computer(stdout) - # Date will be empty -> fallback to current time. - env = await EnvironmentResolver(computer).resolve() - assert isinstance(env.today_date, datetime) + with pytest.raises(ValueError, match="empty datetime"): + await EnvironmentResolver(computer).resolve() async def test_darwin_platform(self) -> None: computer = _mock_computer(_make_stdout(platform="darwin", os_version="Darwin 25.3.0")) From 02d8b252cf5da3284897af4aabfa42c591792736 Mon Sep 17 00:00:00 2001 From: "Anqi (Anthony) Tang" Date: Fri, 27 Mar 2026 17:28:43 +0800 Subject: [PATCH 18/28] security: fix CodeQL alerts for clear-text key storage and exception exposure - OnboardingWizard.tsx: exclude API keys (apiKey, sumApiKey, searchKey, fetchKey, e2bKey) from localStorage draft persistence; only non-sensitive form state (provider IDs, model IDs, display names, URLs) is saved - routes/setup.py: sanitize _run_wrapper exception handler to emit a generic error message to the SSE stream instead of raw str(exc) which could expose stack traces or internal paths; full details are still logged server-side via logger.exception() --- .../backend/hexagent_api/routes/setup.py | 36 +++++++++---------- .../src/components/OnboardingWizard.tsx | 20 ----------- 2 files changed, 18 insertions(+), 38 deletions(-) diff --git a/libs/hexagent_demo/backend/hexagent_api/routes/setup.py b/libs/hexagent_demo/backend/hexagent_api/routes/setup.py index b09d0c59..034db6f5 100644 --- a/libs/hexagent_demo/backend/hexagent_api/routes/setup.py +++ b/libs/hexagent_demo/backend/hexagent_api/routes/setup.py @@ -179,11 +179,11 @@ def _lima_status() -> dict[str, object]: # WSL (Windows) # --------------------------------------------------------------------------- -_WSL_INSTANCE = "openagent" +_WSL_INSTANCE = "hexagent" _WSL_EXPORT_SOURCE = "Ubuntu" _WSL_PREBUILT_CANDIDATES = ( - "openagent-prebuilt.tar", - "openagent.tar", + "hexagent-prebuilt.tar", + "hexagent.tar", ) @@ -372,7 +372,7 @@ async def _wsl_probe_start() -> tuple[bool, str]: # ``wsl -l -v`` uses the Windows display language for the STATE column. -# Cowork only needs the ``openagent`` distro to exist; WSL starts it on demand. +# Cowork only needs the ``hexagent`` distro to exist; WSL starts it on demand. _WSL_COWORK_READY_STATES = frozenset( { "Running", @@ -855,11 +855,11 @@ async def _run_wrapper(self, **kwargs: object) -> None: self._status = "error" self._error = "Cancelled" self._emit("error", {"message": "Cancelled"}) - except Exception as exc: + except Exception: logger.exception("%s failed", self.__class__.__name__) self._status = "error" - self._error = str(exc) - self._emit("error", {"message": str(exc)}) + self._error = "Internal error" + self._emit("error", {"message": "An internal error occurred — check server logs for details."}) finally: self._new_event.set() @@ -1049,7 +1049,7 @@ async def _run_wsl(self) -> None: else: err = _combine_wsl_output(stdout_b, stderr_b) if _looks_like_missing_wsl_disk(err): - self._emit("progress", {"step": "creating", "message": "Detected broken WSL distro disk. Recreating OpenAgent distro..."}) + self._emit("progress", {"step": "creating", "message": "Detected broken WSL distro disk. Recreating HexAgent distro..."}) proc_unreg = await asyncio.create_subprocess_exec( wsl_exe, "--unregister", _WSL_INSTANCE, stdout=asyncio.subprocess.PIPE, @@ -1059,7 +1059,7 @@ async def _run_wsl(self) -> None: u_out_b, u_err_b = await self._communicate_with_heartbeat( proc_unreg, step="creating", - message="Removing broken OpenAgent WSL distro...", + message="Removing broken HexAgent WSL distro...", ) if proc_unreg.returncode != 0: u_err = _combine_wsl_output(u_out_b, u_err_b) @@ -1077,9 +1077,9 @@ async def _run_wsl(self) -> None: prebuilt_tar = _wsl_prebuilt_tar_path() import_dir = data_dir() / "wsl" / _WSL_INSTANCE / "disk" - # Distro does not exist: prefer bundled prebuilt OpenAgent rootfs. + # Distro does not exist: prefer bundled prebuilt HexAgent rootfs. if prebuilt_tar is not None: - self._emit("progress", {"step": "creating", "message": "Importing bundled OpenAgent VM image..."}) + self._emit("progress", {"step": "creating", "message": "Importing bundled HexAgent VM image..."}) if import_dir.exists(): shutil.rmtree(import_dir, ignore_errors=True) import_dir.mkdir(parents=True, exist_ok=True) @@ -1093,7 +1093,7 @@ async def _run_wsl(self) -> None: _, err_b = await self._communicate_with_heartbeat( proc_import, step="creating", - message="Importing bundled OpenAgent VM image...", + message="Importing bundled HexAgent VM image...", progress_info=lambda: f"(image ~{(prebuilt_tar.stat().st_size / (1024 * 1024)):.1f} MB)", ) if proc_import.returncode != 0: @@ -1103,7 +1103,7 @@ async def _run_wsl(self) -> None: self._error = f"exit {proc_import.returncode}" return - self._emit("progress", {"step": "starting", "message": "Starting imported OpenAgent WSL distro..."}) + self._emit("progress", {"step": "starting", "message": "Starting imported HexAgent WSL distro..."}) proc_start = await asyncio.create_subprocess_exec( wsl_exe, "-d", _WSL_INSTANCE, "--", "echo", "ok", stdout=asyncio.subprocess.PIPE, @@ -1113,7 +1113,7 @@ async def _run_wsl(self) -> None: out_b, err_b = await self._communicate_with_heartbeat( proc_start, step="starting", - message="Starting imported OpenAgent WSL distro...", + message="Starting imported HexAgent WSL distro...", ) if proc_start.returncode == 0: self._emit("done", {"message": "WSL distro imported from bundled image and started successfully"}) @@ -1187,7 +1187,7 @@ async def _run_wsl(self) -> None: self._error = f"exit {proc_export.returncode}" return - self._emit("progress", {"step": "creating", "message": "Importing OpenAgent WSL distro..."}) + self._emit("progress", {"step": "creating", "message": "Importing HexAgent WSL distro..."}) proc_import = await asyncio.create_subprocess_exec( wsl_exe, "--import", _WSL_INSTANCE, str(import_dir), str(export_tar), "--version", "2", stdout=asyncio.subprocess.PIPE, @@ -1197,7 +1197,7 @@ async def _run_wsl(self) -> None: _, err_b = await self._communicate_with_heartbeat( proc_import, step="creating", - message="Importing OpenAgent WSL distro...", + message="Importing HexAgent WSL distro...", ) if proc_import.returncode != 0: err = _decode_wsl_output(err_b or b"").strip() @@ -1206,7 +1206,7 @@ async def _run_wsl(self) -> None: self._error = f"exit {proc_import.returncode}" return - self._emit("progress", {"step": "starting", "message": "Starting OpenAgent WSL distro..."}) + self._emit("progress", {"step": "starting", "message": "Starting HexAgent WSL distro..."}) proc_start = await asyncio.create_subprocess_exec( wsl_exe, "-d", _WSL_INSTANCE, "--", "echo", "ok", stdout=asyncio.subprocess.PIPE, @@ -1216,7 +1216,7 @@ async def _run_wsl(self) -> None: out_b, err_b = await self._communicate_with_heartbeat( proc_start, step="starting", - message="Starting OpenAgent WSL distro...", + message="Starting HexAgent WSL distro...", ) if proc_start.returncode == 0: self._emit("done", {"message": "WSL distro created and started successfully"}) diff --git a/libs/hexagent_demo/frontend/src/components/OnboardingWizard.tsx b/libs/hexagent_demo/frontend/src/components/OnboardingWizard.tsx index 94298b13..e7aeebd8 100644 --- a/libs/hexagent_demo/frontend/src/components/OnboardingWizard.tsx +++ b/libs/hexagent_demo/frontend/src/components/OnboardingWizard.tsx @@ -89,21 +89,16 @@ const ONBOARDING_DRAFT_KEY = "hexagent-onboarding-draft-v1"; interface OnboardingDraft { step?: Step; selectedProviderId?: string; - apiKey?: string; modelId?: string; displayName?: string; baseUrl?: string; sumProviderId?: string; - sumApiKey?: string; sumModelId?: string; sumDisplayName?: string; sumBaseUrl?: string; sumSameAsMain?: boolean; searchProvider?: string; - searchKey?: string; fetchProvider?: string; - fetchKey?: string; - e2bKey?: string; vmSkipped?: boolean; } @@ -197,21 +192,16 @@ export default function OnboardingWizard({ open, onComplete, settings, onSetting if (draft) { if (draft.step && STEPS.includes(draft.step)) setStep(draft.step); if (draft.selectedProviderId) setSelectedProvider(PROVIDERS.find((p) => p.id === draft.selectedProviderId) ?? null); - if (typeof draft.apiKey === "string") setApiKey(draft.apiKey); if (typeof draft.modelId === "string") setModelId(draft.modelId); if (typeof draft.displayName === "string") setDisplayName(draft.displayName); if (typeof draft.baseUrl === "string") setBaseUrl(draft.baseUrl); if (draft.sumProviderId) setSumProvider(PROVIDERS.find((p) => p.id === draft.sumProviderId) ?? null); - if (typeof draft.sumApiKey === "string") setSumApiKey(draft.sumApiKey); if (typeof draft.sumModelId === "string") setSumModelId(draft.sumModelId); if (typeof draft.sumDisplayName === "string") setSumDisplayName(draft.sumDisplayName); if (typeof draft.sumBaseUrl === "string") setSumBaseUrl(draft.sumBaseUrl); if (typeof draft.sumSameAsMain === "boolean") setSumSameAsMain(draft.sumSameAsMain); if (typeof draft.searchProvider === "string") setSearchProvider(draft.searchProvider); - if (typeof draft.searchKey === "string") setSearchKey(draft.searchKey); if (typeof draft.fetchProvider === "string") setFetchProvider(draft.fetchProvider); - if (typeof draft.fetchKey === "string") setFetchKey(draft.fetchKey); - if (typeof draft.e2bKey === "string") setE2bKey(draft.e2bKey); if (typeof draft.vmSkipped === "boolean") setVmSkipped(draft.vmSkipped); } @@ -223,42 +213,32 @@ export default function OnboardingWizard({ open, onComplete, settings, onSetting saveOnboardingDraft({ step, selectedProviderId: selectedProvider?.id, - apiKey, modelId, displayName, baseUrl, sumProviderId: sumProvider?.id, - sumApiKey, sumModelId, sumDisplayName, sumBaseUrl, sumSameAsMain, searchProvider, - searchKey, fetchProvider, - fetchKey, - e2bKey, vmSkipped, }); }, [ open, step, selectedProvider, - apiKey, modelId, displayName, baseUrl, sumProvider, - sumApiKey, sumModelId, sumDisplayName, sumBaseUrl, sumSameAsMain, searchProvider, - searchKey, fetchProvider, - fetchKey, - e2bKey, vmSkipped, ]); From 3b6d55aab94c78c43bb8e47b4ddce72d13171b30 Mon Sep 17 00:00:00 2001 From: "Anqi (Anthony) Tang" Date: Fri, 27 Mar 2026 17:54:45 +0800 Subject: [PATCH 19/28] fix(wsl): eliminate redundant wsl.exe resolution and preserve exception chain _check_wsl_prerequisites() now returns the validated wsl.exe path so WslVM.__init__ can use it directly, removing the redundant second call to _resolve_wsl_exe() and the assert that would be silently stripped under python -O in packaged builds. In agent_manager, the catch-all cowork setup error now raises from exc instead of from None so unexpected failures carry a full traceback for debugging. Known operational states (distro not found, VM not running) correctly keep from None since their rewrites are intentional. --- libs/hexagent/hexagent/computer/local/_wsl.py | 18 ++++++++++-------- .../backend/hexagent_api/agent_manager.py | 4 ++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/libs/hexagent/hexagent/computer/local/_wsl.py b/libs/hexagent/hexagent/computer/local/_wsl.py index 7a6aa956..b62909a3 100644 --- a/libs/hexagent/hexagent/computer/local/_wsl.py +++ b/libs/hexagent/hexagent/computer/local/_wsl.py @@ -100,8 +100,8 @@ def _ensure_proactor_event_loop() -> None: asyncio.set_event_loop_policy(proactor_cls()) # type: ignore[deprecated,unused-ignore] -def _check_wsl_prerequisites() -> None: - """Verify we are on Windows with WSL available. +def _check_wsl_prerequisites() -> str: + """Verify we are on Windows with WSL available and return the wsl.exe path. Also ensures the ``ProactorEventLoop`` policy is active. On Windows, uvicorn (and some other frameworks) force ``SelectorEventLoop``, which @@ -110,6 +110,9 @@ def _check_wsl_prerequisites() -> None: instantiates ``WslVM`` gets it automatically, regardless of the application entry point. + Returns: + Absolute path to ``wsl.exe``. + Raises: UnsupportedPlatformError: If not on Windows. MissingDependencyError: If ``wsl.exe`` is not found. @@ -117,13 +120,15 @@ def _check_wsl_prerequisites() -> None: if _PLATFORM != "win32": msg = f"WSL is a Windows subsystem — it cannot run on {_PLATFORM}" raise UnsupportedPlatformError(msg) - if _resolve_wsl_exe() is None: + wsl_exe = _resolve_wsl_exe() + if wsl_exe is None: msg = "wsl.exe not found. Install WSL2: https://learn.microsoft.com/windows/wsl/install" raise MissingDependencyError(msg) # Ensure ProactorEventLoop is used so create_subprocess_exec works. # SelectorEventLoop (uvicorn's default on Windows) does not support it. _ensure_proactor_event_loop() + return wsl_exe class WslVM: @@ -134,10 +139,7 @@ class WslVM: """ def __init__(self, instance: str) -> None: - _check_wsl_prerequisites() - wsl_exe = _resolve_wsl_exe() - assert wsl_exe is not None # noqa: S101 - self._wsl_exe = wsl_exe + self._wsl_exe = _check_wsl_prerequisites() self._instance = instance self._unc_prefix: str | None = None # cached after first successful probe @@ -577,7 +579,7 @@ async def _apply_bind_mounts(self) -> None: # Windows ACLs allow writes. chown maps ownership to the session Linux user so # mkdir/Write behave consistently. sess = _session_user_from_guest_mount_path(m.guest_path) - skip_chown = os.environ.get("OPENAGENT_WSL_SKIP_SESSION_MOUNT_CHOWN", "").strip().lower() in ( + skip_chown = os.environ.get("HEXAGENT_WSL_SKIP_SESSION_MOUNT_CHOWN", "").strip().lower() in ( "1", "true", "yes", diff --git a/libs/hexagent_demo/backend/hexagent_api/agent_manager.py b/libs/hexagent_demo/backend/hexagent_api/agent_manager.py index 20a3164e..31384445 100644 --- a/libs/hexagent_demo/backend/hexagent_api/agent_manager.py +++ b/libs/hexagent_demo/backend/hexagent_api/agent_manager.py @@ -74,7 +74,7 @@ async def _verify_session_dir_writable(self, session_name: str, guest_dir: str) if vm is None: return - probe = f"{guest_dir.rstrip('/')}/.openagent_write_probe_{os.getpid()}" + probe = f"{guest_dir.rstrip('/')}/.hexagent_write_probe_{os.getpid()}" cmd = ( f"test -d {shlex.quote(guest_dir)} && " f"test -w {shlex.quote(guest_dir)} && " @@ -210,7 +210,7 @@ async def _ensure_computer( "VM is not running. " "Please set it up in Settings \u2192 Sandbox." ) from None - raise RuntimeError(f"Cowork session setup failed: {detail}") from None + raise RuntimeError(f"Cowork session setup failed: {detail}") from exc actual_name = computer.session_name self._computers[actual_name] = computer From d17cf8d824aeb69548d0d347c76a614e8ab27fef Mon Sep 17 00:00:00 2001 From: xuelin-cell Date: Fri, 27 Mar 2026 18:52:54 +0800 Subject: [PATCH 20/28] security(codeql): avoid exposing exception details in setup routes --- .../hexagent_demo/backend/hexagent_api/routes/setup.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/libs/hexagent_demo/backend/hexagent_api/routes/setup.py b/libs/hexagent_demo/backend/hexagent_api/routes/setup.py index 034db6f5..61d9a89d 100644 --- a/libs/hexagent_demo/backend/hexagent_api/routes/setup.py +++ b/libs/hexagent_demo/backend/hexagent_api/routes/setup.py @@ -266,7 +266,8 @@ def _probe_wsl2_readiness() -> tuple[bool, str | None]: timeout=8, ) except Exception as exc: # pragma: no cover - defensive - return False, str(exc) + logger.warning("WSL readiness probe failed", exc_info=exc) + return False, "Failed to probe WSL runtime" combined = _combine_wsl_output(proc.stdout, proc.stderr).strip() reason = _wsl2_blocker_reason(combined) @@ -456,7 +457,8 @@ def _wsl_status() -> dict[str, object]: timeout=8, ) except Exception as exc: # pragma: no cover - defensive - last_err = str(exc) + logger.warning("WSL status probe failed for args=%s", args, exc_info=exc) + last_err = "Failed to probe WSL runtime" continue if proc.returncode == 0: @@ -592,9 +594,9 @@ def _extract() -> None: _ensure_managed_lima_on_path() yield sse("done", {"message": f"Lima v{version} installed successfully", "path": str(_lima_bin())}) - except Exception as exc: + except Exception: logger.exception("Lima installation failed") - yield sse("error", {"message": str(exc)}) + yield sse("error", {"message": "Lima installation failed. Check server logs for details."}) finally: shutil.rmtree(tmp_dir, ignore_errors=True) From d8261268690c3965a853ea9149b40efe5f113560 Mon Sep 17 00:00:00 2001 From: xuelin-cell Date: Sat, 28 Mar 2026 07:41:41 +0800 Subject: [PATCH 21/28] fix(cowork): harden WSL shell decoding and environment probing --- libs/hexagent/hexagent/computer/local/_wsl.py | 15 ++- libs/hexagent/hexagent/harness/environment.py | 94 ++++++++++++++----- .../hexagent/tools/ui/present_to_user.py | 7 +- .../unit_tests/harness/test_environment.py | 59 +++++++++--- .../tools/ui/test_present_to_user.py | 5 + 5 files changed, 140 insertions(+), 40 deletions(-) diff --git a/libs/hexagent/hexagent/computer/local/_wsl.py b/libs/hexagent/hexagent/computer/local/_wsl.py index b62909a3..4f2dc500 100644 --- a/libs/hexagent/hexagent/computer/local/_wsl.py +++ b/libs/hexagent/hexagent/computer/local/_wsl.py @@ -45,6 +45,13 @@ _PLATFORM = sys.platform +def _decode_wsl_output(raw: bytes) -> str: + """Decode WSL output that may be UTF-16-LE on some Windows builds.""" + if raw[:2] == b"\xff\xfe" or b"\x00" in raw: + return raw.decode("utf-16-le", errors="replace").replace("\x00", "") + return raw.decode("utf-8", errors="replace") + + def _resolve_wsl_exe() -> str | None: """Return a usable ``wsl.exe`` path. @@ -453,8 +460,8 @@ async def shell( msg = f"timed out after {timeout}s" raise WslError(msg) from None - stdout = stdout_bytes.decode("utf-8", errors="replace").removesuffix("\n") - stderr = stderr_bytes.decode("utf-8", errors="replace").removesuffix("\n") + stdout = _decode_wsl_output(stdout_bytes).removesuffix("\n") + stderr = _decode_wsl_output(stderr_bytes).removesuffix("\n") rc: int = process.returncode if process.returncode is not None else -1 return CLIResult( @@ -530,11 +537,11 @@ async def _run_wsl(self, *cmd: str, timeout: float = 300) -> str: # noqa: ASYNC raise WslError(msg) from None if proc.returncode != 0: - stderr = stderr_bytes.decode("utf-8", errors="replace").strip() + stderr = _decode_wsl_output(stderr_bytes).strip() msg = f"wsl.exe failed (exit {proc.returncode}): {stderr}" raise WslError(msg) - return stdout_bytes.decode("utf-8", errors="replace") + return _decode_wsl_output(stdout_bytes) async def _apply_bind_mounts(self) -> None: """Apply all bind mounts from ``mounts.json`` inside the distro. diff --git a/libs/hexagent/hexagent/harness/environment.py b/libs/hexagent/hexagent/harness/environment.py index 9aa63886..7d6fbc6e 100644 --- a/libs/hexagent/hexagent/harness/environment.py +++ b/libs/hexagent/hexagent/harness/environment.py @@ -6,6 +6,8 @@ from __future__ import annotations +import logging +import shlex from datetime import datetime from typing import TYPE_CHECKING @@ -14,6 +16,8 @@ if TYPE_CHECKING: from hexagent.computer.base import Computer +logger = logging.getLogger(__name__) + class EnvironmentResolver: """Detects runtime environment properties via a Computer. @@ -38,6 +42,48 @@ def __init__(self, computer: Computer) -> None: """ self._computer = computer + async def _probe_datetime(self) -> datetime: + """Best-effort datetime probe that never raises. + + Returns: + Timezone-aware datetime when possible; falls back to UTC now. + """ + # Primary probe: timezone-aware ISO-8601 from shell date. + probe = await self._computer.run("date '+%Y-%m-%dT%H:%M:%S%z'") + raw = (probe.stdout or "").strip() + if raw: + try: + return datetime.strptime(raw, "%Y-%m-%dT%H:%M:%S%z") + except ValueError: + try: + return datetime.strptime(raw[:19], "%Y-%m-%dT%H:%M:%S") # noqa: DTZ007 + except ValueError: + logger.warning("Unparseable environment datetime probe: %r", raw) + + # Secondary probe: Python inside guest (if available). + py_probe = await self._computer.run( + "python3 -c \"from datetime import datetime as d; print(d.now().astimezone().strftime('%Y-%m-%dT%H:%M:%S%z'))\"" + ) + py_raw = (py_probe.stdout or "").strip() + if py_raw: + try: + return datetime.strptime(py_raw, "%Y-%m-%dT%H:%M:%S%z") + except ValueError: + try: + return datetime.strptime(py_raw[:19], "%Y-%m-%dT%H:%M:%S") # noqa: DTZ007 + except ValueError: + logger.warning("Unparseable python datetime probe: %r", py_raw) + + logger.warning( + "Environment datetime probes failed; falling back to UTC now. " + "date.stdout=%r date.stderr=%r python3.stdout=%r python3.stderr=%r", + probe.stdout, + probe.stderr, + py_probe.stdout, + py_probe.stderr, + ) + return datetime.now().astimezone() + async def resolve(self) -> EnvironmentContext: """Detect environment properties from the computer. @@ -46,19 +92,19 @@ async def resolve(self) -> EnvironmentContext: """ # Single batched command: 6 values separated by a unique delimiter. delimiter = "___ENV___" + qd = shlex.quote(delimiter) cmd = ( - f'printf "%s\\n" ' - f'"$(pwd)" ' - f'"{delimiter}" ' - f'"$(git rev-parse --is-inside-work-tree 2>/dev/null || echo false)" ' - f'"{delimiter}" ' - f'"$(uname -s | tr "[:upper:]" "[:lower:]")" ' - f'"{delimiter}" ' - f'"$(basename "$SHELL")" ' - f'"{delimiter}" ' - f'"$(uname -sr)" ' - f'"{delimiter}" ' - f"\"$(date '+%Y-%m-%dT%H:%M:%S%z')\"" + "pwd; " + f"printf '%s\\n' {qd}; " + "(git rev-parse --is-inside-work-tree 2>/dev/null || echo false); " + f"printf '%s\\n' {qd}; " + "uname -s | tr '[:upper:]' '[:lower:]'; " + f"printf '%s\\n' {qd}; " + "basename \"${SHELL:-bash}\"; " + f"printf '%s\\n' {qd}; " + "uname -sr; " + f"printf '%s\\n' {qd}; " + "date '+%Y-%m-%dT%H:%M:%S%z'" ) result = await self._computer.run(cmd) parts = result.stdout.strip().split(delimiter) @@ -69,16 +115,22 @@ async def resolve(self) -> EnvironmentContext: while len(values) < _EXPECTED_PARTS: values.append("") - # Parse into a timezone-aware datetime. - # Shell outputs ISO 8601 with numeric offset, e.g. "2026-02-14T10:30:00-0800". + # Parse into a datetime. Shell usually outputs timezone-aware ISO 8601, + # e.g. "2026-02-14T10:30:00-0800". If missing, probe separately. raw_dt = values[5] - if not raw_dt: - msg = f"Environment shell returned empty datetime (raw output: {result.stdout!r})" - raise ValueError(msg) - try: - now = datetime.strptime(raw_dt, "%Y-%m-%dT%H:%M:%S%z") - except ValueError: - now = datetime.strptime(raw_dt[:19], "%Y-%m-%dT%H:%M:%S") # noqa: DTZ007 + if raw_dt: + try: + now = datetime.strptime(raw_dt, "%Y-%m-%dT%H:%M:%S%z") + except ValueError: + now = datetime.strptime(raw_dt[:19], "%Y-%m-%dT%H:%M:%S") # noqa: DTZ007 + else: + logger.warning( + "Environment shell returned empty datetime; falling back probe. stdout=%r stderr=%r exit=%s", + result.stdout, + result.stderr, + result.exit_code, + ) + now = await self._probe_datetime() return EnvironmentContext( working_dir=values[0], diff --git a/libs/hexagent/hexagent/tools/ui/present_to_user.py b/libs/hexagent/hexagent/tools/ui/present_to_user.py index b9b5df39..7efeb826 100644 --- a/libs/hexagent/hexagent/tools/ui/present_to_user.py +++ b/libs/hexagent/hexagent/tools/ui/present_to_user.py @@ -184,6 +184,11 @@ def _build_case_block() -> str: done """.format(case_arms=_build_case_block()) # noqa: UP032 — can't use f-string; bash ${} conflicts +# WSL/bash is sensitive to CRLF in inline scripts (can break function +# definitions/quoting with opaque parse errors). Normalize to LF at runtime so +# behavior is stable regardless of host checkout EOL settings. +_SCRIPT_BODY_LF = _SCRIPT_BODY.replace("\r\n", "\n").replace("\r", "\n") + def _build_command(filepaths: list[str], output_dir: str) -> str: """Build a bash command that processes all file paths. @@ -200,7 +205,7 @@ def _build_command(filepaths: list[str], output_dir: str) -> str: A shell command string safe for ``Computer.run()``. """ quoted_args = " ".join(shlex.quote(p) for p in [output_dir, *filepaths]) - return f"bash -c {shlex.quote(_SCRIPT_BODY)} _ {quoted_args}" + return f"bash -c {shlex.quote(_SCRIPT_BODY_LF)} _ {quoted_args}" class PresentToUserTool(BaseAgentTool[PresentToUserToolParams]): diff --git a/libs/hexagent/tests/unit_tests/harness/test_environment.py b/libs/hexagent/tests/unit_tests/harness/test_environment.py index 9681f0a6..e41f9b32 100644 --- a/libs/hexagent/tests/unit_tests/harness/test_environment.py +++ b/libs/hexagent/tests/unit_tests/harness/test_environment.py @@ -1,4 +1,4 @@ -# ruff: noqa: PLR2004 +# ruff: noqa: PLR2004 """Tests for EnvironmentResolver.""" from __future__ import annotations @@ -6,8 +6,6 @@ from datetime import UTC, datetime from unittest.mock import AsyncMock -import pytest - from hexagent.harness.environment import EnvironmentResolver from hexagent.types import CLIResult @@ -36,6 +34,12 @@ def _mock_computer(stdout: str) -> AsyncMock: return computer +def _mock_computer_sequence(results: list[CLIResult]) -> AsyncMock: + computer = AsyncMock() + computer.run = AsyncMock(side_effect=results) + return computer + + # --------------------------------------------------------------------------- # Tests # --------------------------------------------------------------------------- @@ -83,20 +87,47 @@ async def test_datetime_without_timezone_fallback(self) -> None: assert env.today_date.year == 2026 assert env.today_date.tzinfo is None - async def test_empty_datetime_raises(self) -> None: - """Empty datetime must raise — it indicates a broken shell probe.""" - computer = _mock_computer(_make_stdout(date="")) - with pytest.raises(ValueError, match="empty datetime"): - await EnvironmentResolver(computer).resolve() + async def test_empty_datetime_falls_back_to_secondary_probe(self) -> None: + """Empty datetime in batched output falls back to a secondary date probe.""" + computer = _mock_computer_sequence( + [ + CLIResult(stdout=_make_stdout(date=""), stderr="", exit_code=0), + CLIResult(stdout="2026-03-13T10:30:00+0000", stderr="", exit_code=0), + ] + ) + env = await EnvironmentResolver(computer).resolve() + + assert env.today_date.tzinfo is not None + assert env.today_date == datetime(2026, 3, 13, 10, 30, 0, tzinfo=UTC) - async def test_pads_missing_parts_raises(self) -> None: - """When stdout has fewer delimiters, missing date field raises.""" - # Only cwd and git — missing platform, shell, os_version, date + async def test_pads_missing_parts_falls_back_to_secondary_probe(self) -> None: + """When stdout has fewer delimiters, resolver still recovers via date probe.""" + # Only cwd and git; missing platform, shell, os_version, date stdout = f"/home/user\n{_DELIM}\ntrue" - computer = _mock_computer(stdout) + computer = _mock_computer_sequence( + [ + CLIResult(stdout=stdout, stderr="", exit_code=0), + CLIResult(stdout="2026-03-13T10:30:00+0000", stderr="", exit_code=0), + ] + ) + env = await EnvironmentResolver(computer).resolve() + + assert env.today_date.tzinfo is not None + assert env.today_date.year == 2026 + + async def test_all_datetime_probes_fail_uses_local_time(self) -> None: + """If both date probes fail, resolver should still return a usable context.""" + computer = _mock_computer_sequence( + [ + CLIResult(stdout=_make_stdout(date=""), stderr="", exit_code=0), + CLIResult(stdout="", stderr="date: command not found", exit_code=127), + CLIResult(stdout="", stderr="python3: command not found", exit_code=127), + ] + ) + env = await EnvironmentResolver(computer).resolve() - with pytest.raises(ValueError, match="empty datetime"): - await EnvironmentResolver(computer).resolve() + assert env.today_date.tzinfo is not None + assert env.today_date.year >= 2020 async def test_darwin_platform(self) -> None: computer = _mock_computer(_make_stdout(platform="darwin", os_version="Darwin 25.3.0")) diff --git a/libs/hexagent/tests/unit_tests/tools/ui/test_present_to_user.py b/libs/hexagent/tests/unit_tests/tools/ui/test_present_to_user.py index b8f21263..3e68db1f 100644 --- a/libs/hexagent/tests/unit_tests/tools/ui/test_present_to_user.py +++ b/libs/hexagent/tests/unit_tests/tools/ui/test_present_to_user.py @@ -160,6 +160,11 @@ def test_quotes_special_characters(self) -> None: cmd = _build_command(["/path/with spaces/file.txt"], "/out") assert "'/path/with spaces/file.txt'" in cmd + def test_embedded_script_normalized_to_lf(self) -> None: + """Command string should not carry CR characters into bash -c payload.""" + cmd = _build_command(["/a.txt"], "/out") + assert "\r" not in cmd + # --------------------------------------------------------------------------- # _EXT_MIME_MAP / generated script tests From 32b2fd05754f9bc3ef879a4cad646ceac1af45c5 Mon Sep 17 00:00:00 2001 From: xuelin-cell Date: Sat, 28 Mar 2026 07:41:52 +0800 Subject: [PATCH 22/28] feat(setup): improve WSL instance/provision reliability on Windows --- .../backend/hexagent_api/routes/setup.py | 221 +++++++++++++----- libs/hexagent_demo/frontend/src/App.tsx | 2 +- libs/hexagent_demo/frontend/src/vmSetup.tsx | 13 ++ 3 files changed, 175 insertions(+), 61 deletions(-) diff --git a/libs/hexagent_demo/backend/hexagent_api/routes/setup.py b/libs/hexagent_demo/backend/hexagent_api/routes/setup.py index 61d9a89d..4a2137d0 100644 --- a/libs/hexagent_demo/backend/hexagent_api/routes/setup.py +++ b/libs/hexagent_demo/backend/hexagent_api/routes/setup.py @@ -184,6 +184,7 @@ def _lima_status() -> dict[str, object]: _WSL_PREBUILT_CANDIDATES = ( "hexagent-prebuilt.tar", "hexagent.tar", + "ubuntu-base-24.04-amd64.tar.gz", ) @@ -222,6 +223,18 @@ def _combine_wsl_output(stdout_b: bytes | None, stderr_b: bytes | None) -> str: return err or out +def _looks_like_wsl_usage(msg: str) -> bool: + """Return True when output is the generic WSL usage/help banner.""" + text = msg.strip() + low = text.lower() + return ( + "usage: wsl" in low + or "usage: wsl.exe" in low + or "用法: wsl" in text + or "用法: wsl.exe" in text + ) + + def _looks_like_missing_wsl_disk(msg: str) -> bool: text = msg.lower() return ( @@ -333,12 +346,23 @@ async def _wsl_instance_status() -> str | None: def _wsl_prebuilt_tar_path() -> Path | None: - """Return bundled prebuilt WSL rootfs tar if present.""" - prebuilt_dir = vm_setup_dir().parent / "wsl" / "prebuilt" - for name in _WSL_PREBUILT_CANDIDATES: - candidate = prebuilt_dir / name - if candidate.is_file(): - return candidate + """Return an offline WSL rootfs archive if present. + + Search order: + 1. Backend-bundled VM assets (PyInstaller ``sandbox/vm/wsl/prebuilt``) + 2. Electron extraResources path from ``HEXAGENT_WSL_OFFLINE_DIR`` (if set) + """ + candidate_dirs: list[Path] = [vm_setup_dir().parent / "wsl" / "prebuilt"] + + offline_dir = os.environ.get("HEXAGENT_WSL_OFFLINE_DIR", "").strip() + if offline_dir: + candidate_dirs.append(Path(offline_dir)) + + for prebuilt_dir in candidate_dirs: + for name in _WSL_PREBUILT_CANDIDATES: + candidate = prebuilt_dir / name + if candidate.is_file(): + return candidate return None @@ -469,7 +493,9 @@ def _wsl_status() -> dict[str, object]: "installed": False, "path": wsl, "managed": False, - "reason": last_err or "WSL runtime is not available", + # Some Windows builds print only usage text for unsupported probes. + # Treat that as "not installed yet" (pending) instead of hard error. + **({} if _looks_like_wsl_usage(last_err) else {"reason": last_err or "WSL runtime is not available"}), } @@ -664,6 +690,22 @@ def _vm_status() -> dict[str, object]: return {"supported": False, "backend": None, "installed": False, "reason": f"No VM backend for {sys.platform}"} +def _runtime_vm_backend() -> str: + """Resolve the active VM backend for branch dispatch. + + Prefer the backend reported by ``_vm_status()`` so behavior stays aligned + with the setup API surface. Fall back to platform defaults defensively. + """ + backend = str(_vm_status().get("backend") or "") + if backend in {"wsl", "lima"}: + return backend + if sys.platform == "win32": + return "wsl" + if sys.platform == "darwin": + return "lima" + return "" + + # --------------------------------------------------------------------------- # Endpoints — generic /vm, frontend doesn't need to know Lima vs WSL # --------------------------------------------------------------------------- @@ -947,10 +989,59 @@ async def _communicate_with_heartbeat( self._emit("progress", {"step": step, "message": f"{message} (elapsed {elapsed}s){extra}"}) async def _run(self, **kwargs: object) -> None: - if sys.platform == "win32": + backend = _runtime_vm_backend() + if backend == "wsl": await self._run_wsl() return - await self._run_lima() + if backend == "lima": + await self._run_lima() + return + self._emit("error", {"message": f"VM build is not supported on backend: {backend or sys.platform}"}) + self._status = "error" + self._error = "Unsupported backend" + + async def _start_wsl_instance( + self, + wsl_exe: str, + *, + step: str, + message: str, + retries_on_missing_disk: int = 0, + ) -> tuple[bool, str]: + """Start hexagent distro and optionally retry transient missing-disk errors.""" + attempts = max(1, retries_on_missing_disk + 1) + for attempt in range(1, attempts + 1): + proc = await asyncio.create_subprocess_exec( + wsl_exe, "-d", _WSL_INSTANCE, "--", "echo", "ok", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + self._process = proc + out_b, err_b = await self._communicate_with_heartbeat( + proc, + step=step, + message=message, + ) + if proc.returncode == 0: + return True, "" + + err = _combine_wsl_output(out_b, err_b) + is_missing_disk = _looks_like_missing_wsl_disk(err) + if is_missing_disk and attempt < attempts: + wait_s = min(2 * attempt, 5) + self._emit( + "progress", + { + "step": step, + "message": f"WSL disk not ready yet, retrying start in {wait_s}s " + f"({attempt}/{attempts - 1})...", + }, + ) + await asyncio.sleep(wait_s) + continue + + return False, err or f"WSL start failed (exit {proc.returncode})" + return False, "WSL start failed" async def _run_lima(self) -> None: # Ensure limactl has the virtualization entitlement before any VM @@ -1033,23 +1124,17 @@ async def _run_wsl(self) -> None: if _wsl_state_equals(status, _WSL_STOPPED_STATES): self._emit("progress", {"step": "starting", "message": "Starting existing WSL distro..."}) - proc = await asyncio.create_subprocess_exec( - wsl_exe, "-d", _WSL_INSTANCE, "--", "echo", "ok", - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - self._process = proc - stdout_b, stderr_b = await self._communicate_with_heartbeat( - proc, + ok, err = await self._start_wsl_instance( + wsl_exe, step="starting", message="Starting existing WSL distro...", + retries_on_missing_disk=1, ) - if proc.returncode == 0: + if ok: self._emit("done", {"message": "WSL distro started successfully"}) self._status = "done" return else: - err = _combine_wsl_output(stdout_b, stderr_b) if _looks_like_missing_wsl_disk(err): self._emit("progress", {"step": "creating", "message": "Detected broken WSL distro disk. Recreating HexAgent distro..."}) proc_unreg = await asyncio.create_subprocess_exec( @@ -1106,25 +1191,19 @@ async def _run_wsl(self) -> None: return self._emit("progress", {"step": "starting", "message": "Starting imported HexAgent WSL distro..."}) - proc_start = await asyncio.create_subprocess_exec( - wsl_exe, "-d", _WSL_INSTANCE, "--", "echo", "ok", - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - self._process = proc_start - out_b, err_b = await self._communicate_with_heartbeat( - proc_start, + ok, err = await self._start_wsl_instance( + wsl_exe, step="starting", message="Starting imported HexAgent WSL distro...", + retries_on_missing_disk=3, ) - if proc_start.returncode == 0: + if ok: self._emit("done", {"message": "WSL distro imported from bundled image and started successfully"}) self._status = "done" else: - err = _combine_wsl_output(out_b, err_b) - self._emit("error", {"message": err or f"WSL start failed (exit {proc_start.returncode})"}) + self._emit("error", {"message": err}) self._status = "error" - self._error = f"exit {proc_start.returncode}" + self._error = err return # Fallback: bootstrap from Ubuntu export. @@ -1209,25 +1288,19 @@ async def _run_wsl(self) -> None: return self._emit("progress", {"step": "starting", "message": "Starting HexAgent WSL distro..."}) - proc_start = await asyncio.create_subprocess_exec( - wsl_exe, "-d", _WSL_INSTANCE, "--", "echo", "ok", - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - self._process = proc_start - out_b, err_b = await self._communicate_with_heartbeat( - proc_start, + ok, err = await self._start_wsl_instance( + wsl_exe, step="starting", message="Starting HexAgent WSL distro...", + retries_on_missing_disk=3, ) - if proc_start.returncode == 0: + if ok: self._emit("done", {"message": "WSL distro created and started successfully"}) self._status = "done" else: - err = _combine_wsl_output(out_b, err_b) - self._emit("error", {"message": err or f"WSL start failed (exit {proc_start.returncode})"}) + self._emit("error", {"message": err}) self._status = "error" - self._error = f"exit {proc_start.returncode}" + self._error = err async def _stream_stderr(self, proc: asyncio.subprocess.Process) -> str: """Read limactl stderr line-by-line and emit progress events. @@ -1260,8 +1333,8 @@ async def _stream_stderr(self, proc: asyncio.subprocess.Process) -> str: # Provision Manager — runs setup.sh inside the VM # --------------------------------------------------------------------------- -_SETUP_MARKER_DIR = "/var/lib/hexagent/setup" -_SETUP_LOG_DIR = "/var/log/hexagent/setup" +_SETUP_MARKER_DIRS = ("/var/lib/hexagent/setup", "/var/lib/openagent/setup") +_SETUP_LOG_DIRS = ("/var/log/hexagent/setup", "/var/log/openagent/setup") _SETUP_VM_DIR = "/tmp/hexagent-setup" # Step IDs that setup.sh discovers (must match filenames in steps/) @@ -1281,10 +1354,16 @@ class _ProvisionManager(_ProcessManager): """Manages setup.sh execution inside the Lima VM.""" async def _run(self, **kwargs: object) -> None: - if sys.platform == "win32": + backend = _runtime_vm_backend() + if backend == "wsl": await self._run_wsl(**kwargs) return - await self._run_lima(**kwargs) + if backend == "lima": + await self._run_lima(**kwargs) + return + self._emit("error", {"message": f"VM provisioning is not supported on backend: {backend or sys.platform}"}) + self._status = "error" + self._error = "Unsupported backend" async def _run_lima(self, **kwargs: object) -> None: force = bool(kwargs.get("force", False)) @@ -1418,7 +1497,9 @@ async def _run_wsl(self, **kwargs: object) -> None: setup_vm_dir_quoted = shlex.quote(_SETUP_VM_DIR) rc, _, err = await _wsl_shell( f"rm -rf {setup_vm_dir_quoted} && mkdir -p {setup_vm_dir_quoted} && " - f"cp -r {setup_wsl_quoted}/. {setup_vm_dir_quoted}/", + f"cp -r {setup_wsl_quoted}/. {setup_vm_dir_quoted}/ && " + f"find {setup_vm_dir_quoted} -type f -name '*.sh' -exec sed -i 's/\\r$//' {{}} + && " + f"find {setup_vm_dir_quoted} -type f -name '*.sh' -exec chmod +x {{}} +", timeout=60, user="root", ) @@ -1488,19 +1569,27 @@ def _handle_setup_line(self, line: str) -> None: async def check_markers(self) -> dict[str, object]: """Read VM-side marker files to determine provision state.""" - if sys.platform == "win32": + backend = _runtime_vm_backend() + if backend == "wsl": instance_status = await _wsl_instance_status() shell = lambda cmd: _wsl_shell(cmd, user="root") if not _wsl_distro_ready_for_cowork(instance_status): return {"provisioned": False, "steps_done": [], "total_steps": len(_PROVISION_STEPS)} - else: + elif backend == "lima": instance_status = await _lima_instance_status() shell = _lima_shell if instance_status != "Running": return {"provisioned": False, "steps_done": [], "total_steps": len(_PROVISION_STEPS)} + else: + return {"provisioned": False, "steps_done": [], "total_steps": len(_PROVISION_STEPS)} - rc, stdout, _ = await shell(f"ls {_SETUP_MARKER_DIR}/*.done 2>/dev/null || true") - if rc != 0 or not stdout.strip(): + stdout = "" + for marker_dir in _SETUP_MARKER_DIRS: + rc, out, _ = await shell(f"ls {marker_dir}/*.done 2>/dev/null || true") + if rc == 0 and out.strip(): + stdout = out + break + if not stdout.strip(): return {"provisioned": False, "steps_done": [], "total_steps": len(_PROVISION_STEPS)} done_files = stdout.strip().splitlines() @@ -1518,12 +1607,21 @@ async def check_markers(self) -> dict[str, object]: async def get_log(self) -> str: """Fetch the latest setup log from the VM.""" - shell = (lambda cmd, timeout=15: _wsl_shell(cmd, timeout=timeout, user="root")) if sys.platform == "win32" else _lima_shell - rc, stdout, _ = await shell( - f"ls -t {_SETUP_LOG_DIR}/setup-*.log 2>/dev/null | head -1 | xargs cat 2>/dev/null | tail -500", - timeout=15, - ) - return stdout if rc == 0 else "" + backend = _runtime_vm_backend() + if backend == "wsl": + shell = lambda cmd, timeout=15: _wsl_shell(cmd, timeout=timeout, user="root") + elif backend == "lima": + shell = _lima_shell + else: + return "" + for log_dir in _SETUP_LOG_DIRS: + rc, stdout, _ = await shell( + f"ls -t {log_dir}/setup-*.log 2>/dev/null | head -1 | xargs cat 2>/dev/null | tail -500", + timeout=15, + ) + if rc == 0 and stdout.strip(): + return stdout + return "" # --------------------------------------------------------------------------- @@ -1573,10 +1671,13 @@ async def get_build_status() -> dict[str, object]: mgr = _get_build_manager() result = dict(mgr.status_dict()) if mgr._status in ("idle", "done", "error"): - if sys.platform == "win32": + backend = _runtime_vm_backend() + if backend == "wsl": result["vm_state"] = await _wsl_instance_status() - else: + elif backend == "lima": result["vm_state"] = await _lima_instance_status() + else: + result["vm_state"] = None return result diff --git a/libs/hexagent_demo/frontend/src/App.tsx b/libs/hexagent_demo/frontend/src/App.tsx index e3c7d180..dd9bcb0d 100644 --- a/libs/hexagent_demo/frontend/src/App.tsx +++ b/libs/hexagent_demo/frontend/src/App.tsx @@ -307,7 +307,7 @@ function App() { abortMapRef.current.set(conversationId, controller); }, - [dispatch, state.conversations, state.selectedModelId, state.isStreaming] + [dispatch, state.conversations, state.selectedModelId] ); const handleNewConversation = useCallback(() => { diff --git a/libs/hexagent_demo/frontend/src/vmSetup.tsx b/libs/hexagent_demo/frontend/src/vmSetup.tsx index ea3797c6..d3a22479 100644 --- a/libs/hexagent_demo/frontend/src/vmSetup.tsx +++ b/libs/hexagent_demo/frontend/src/vmSetup.tsx @@ -109,6 +109,7 @@ export function VMSetupProvider({ children }: { children: ReactNode }) { const [provStepMsg, setProvStepMsg] = useState>({}); const [provLog, setProvLog] = useState(null); const autoBootstrapTriggeredRef = useRef(false); + const autoProvisionTriggeredRef = useRef(false); const [autoBootstrapping, setAutoBootstrapping] = useState(false); // SSE abort controllers (kept alive across renders, never aborted on unmount) @@ -550,6 +551,18 @@ export function VMSetupProvider({ children }: { children: ReactNode }) { } }, [autoBootstrapping, phase1, phase2]); + // Windows-first run: once VM instance is ready, auto start dependency provision + // in background so users don't need to click "Install in background" manually. + useEffect(() => { + if (!IS_WINDOWS) return; + if (autoProvisionTriggeredRef.current) return; + if (phase1 !== "done" || phase2 !== "done") return; + if (phase3 !== "pending") return; + autoProvisionTriggeredRef.current = true; + notify("VM instance is ready. Starting system dependency installation in background...", "info"); + doStartProvision(false); + }, [phase1, phase2, phase3]); // eslint-disable-line react-hooks/exhaustive-deps + const value: VMSetupContextValue = { vmStatus, autoBootstrapping, From 34d8d4417b29aee668bfb89fb0978da5c538f5e0 Mon Sep 17 00:00:00 2001 From: xuelin-cell Date: Sat, 28 Mar 2026 07:42:16 +0800 Subject: [PATCH 23/28] feat(electron): bundle offline WSL assets and build pipeline --- .gitattributes | 7 + libs/hexagent_demo/electron/main.js | 4 + libs/hexagent_demo/electron/package.json | 6 + .../electron/resources/wsl/.gitkeep | 1 + .../wsl/ubuntu-base-24.04-amd64.tar.gz | 3 + .../resources/wsl/wsl.2.6.3.0.x64.msi | 3 + .../electron/scripts/build-all.ps1 | 19 ++- .../scripts/prepare-wsl-offline-assets.ps1 | 127 ++++++++++++++++++ .../electron/wsl.2.6.3.0.x64.msi | 3 + 9 files changed, 168 insertions(+), 5 deletions(-) create mode 100644 libs/hexagent_demo/electron/resources/wsl/.gitkeep create mode 100644 libs/hexagent_demo/electron/resources/wsl/ubuntu-base-24.04-amd64.tar.gz create mode 100644 libs/hexagent_demo/electron/resources/wsl/wsl.2.6.3.0.x64.msi create mode 100644 libs/hexagent_demo/electron/scripts/prepare-wsl-offline-assets.ps1 create mode 100644 libs/hexagent_demo/electron/wsl.2.6.3.0.x64.msi diff --git a/.gitattributes b/.gitattributes index c3482579..245dd5cd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,4 +2,11 @@ libs/openagent/sandbox/vm/setup/*.sh text eol=lf libs/openagent/sandbox/vm/setup/steps/*.sh text eol=lf libs/hexagent/sandbox/vm/setup/*.sh text eol=lf libs/hexagent/sandbox/vm/setup/steps/*.sh text eol=lf +libs/openagent/sandbox/vm/setup_lite/*.sh text eol=lf +libs/openagent/sandbox/vm/setup_lite/steps/*.sh text eol=lf +libs/hexagent/sandbox/vm/setup_lite/*.sh text eol=lf +libs/hexagent/sandbox/vm/setup_lite/steps/*.sh text eol=lf libs/openagent/sandbox/vm/wsl/prebuilt/openagent-prebuilt.tar filter=lfs diff=lfs merge=lfs -text +libs/hexagent_demo/electron/resources/wsl/*.msi filter=lfs diff=lfs merge=lfs -text +libs/hexagent_demo/electron/*.msi filter=lfs diff=lfs merge=lfs -text +libs/hexagent_demo/electron/resources/wsl/ubuntu-base-24.04-amd64.tar.gz filter=lfs diff=lfs merge=lfs -text diff --git a/libs/hexagent_demo/electron/main.js b/libs/hexagent_demo/electron/main.js index b65a9061..b49a2638 100644 --- a/libs/hexagent_demo/electron/main.js +++ b/libs/hexagent_demo/electron/main.js @@ -107,6 +107,9 @@ function waitForHealth(port, retries = 30, interval = 500) { async function spawnBackend() { const port = IS_DEV ? 8000 : await findFreePort(); backendPort = port; + const wslOfflineDir = IS_DEV + ? path.join(__dirname, "resources", "wsl") + : path.join(process.resourcesPath, "wsl"); if (IS_DEV) { const backendDir = path.join(__dirname, "..", "backend"); @@ -167,6 +170,7 @@ async function spawnBackend() { HOST: "127.0.0.1", PORT: String(port), HEXAGENT_DATA_DIR: userDataDir, + HEXAGENT_WSL_OFFLINE_DIR: wslOfflineDir, }, }); } diff --git a/libs/hexagent_demo/electron/package.json b/libs/hexagent_demo/electron/package.json index 75fd69b7..2cd16416 100644 --- a/libs/hexagent_demo/electron/package.json +++ b/libs/hexagent_demo/electron/package.json @@ -64,6 +64,12 @@ }, "afterPack": "./scripts/afterPack.js", "win": { + "extraResources": [ + { + "from": "resources/wsl/", + "to": "wsl" + } + ], "target": [ { "target": "nsis", diff --git a/libs/hexagent_demo/electron/resources/wsl/.gitkeep b/libs/hexagent_demo/electron/resources/wsl/.gitkeep new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/libs/hexagent_demo/electron/resources/wsl/.gitkeep @@ -0,0 +1 @@ + diff --git a/libs/hexagent_demo/electron/resources/wsl/ubuntu-base-24.04-amd64.tar.gz b/libs/hexagent_demo/electron/resources/wsl/ubuntu-base-24.04-amd64.tar.gz new file mode 100644 index 00000000..24e69628 --- /dev/null +++ b/libs/hexagent_demo/electron/resources/wsl/ubuntu-base-24.04-amd64.tar.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c1e67ef7b17a6300e136118bd1dc04725009cb376c1aad10abcf8cd453628d58 +size 29989394 diff --git a/libs/hexagent_demo/electron/resources/wsl/wsl.2.6.3.0.x64.msi b/libs/hexagent_demo/electron/resources/wsl/wsl.2.6.3.0.x64.msi new file mode 100644 index 00000000..3a090704 --- /dev/null +++ b/libs/hexagent_demo/electron/resources/wsl/wsl.2.6.3.0.x64.msi @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:562c79aba6ce9b6e9170f069d31e3717f10d76dd8bfbee39b07eae0ca4a02ca0 +size 247123968 diff --git a/libs/hexagent_demo/electron/scripts/build-all.ps1 b/libs/hexagent_demo/electron/scripts/build-all.ps1 index d26a5905..3c3f9e01 100644 --- a/libs/hexagent_demo/electron/scripts/build-all.ps1 +++ b/libs/hexagent_demo/electron/scripts/build-all.ps1 @@ -3,7 +3,8 @@ $ErrorActionPreference = 'Stop' $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $ElectronDir = Resolve-Path "$ScriptDir\.." $Target = if ($args.Count -gt 0) { $args[0] } else { 'win' } -$EmbedWslPrebuilt = ($env:OPENAGENT_EMBED_WSL_PREBUILT -eq "1") +$EmbedWslPrebuilt = ($env:HEXAGENT_EMBED_WSL_PREBUILT -eq "1" -or $env:OPENAGENT_EMBED_WSL_PREBUILT -eq "1") +$PrepareOfflineWsl = ($env:HEXAGENT_PREPARE_OFFLINE_WSL -ne "0") Write-Host '=========================================' Write-Host ' HexAgent Desktop - Build ('$Target')' @@ -25,10 +26,18 @@ Write-Host '' Write-Host '[2/3] Skipping electron dependencies (already installed)...' Set-Location $ElectronDir -if ($Target -eq 'win' -and $EmbedWslPrebuilt) { - Write-Host '' - Write-Host '[2.2/3] Exporting prebuilt WSL VM image for offline-ready package...' - & "$ScriptDir\prepare-wsl-prebuilt.ps1" +if ($Target -eq 'win') { + if ($PrepareOfflineWsl) { + Write-Host '' + Write-Host '[2.1/3] Preparing offline WSL installer assets...' + & "$ScriptDir\prepare-wsl-offline-assets.ps1" + } + + if ($EmbedWslPrebuilt) { + Write-Host '' + Write-Host '[2.2/3] Exporting prebuilt WSL VM image for offline-ready package...' + & "$ScriptDir\prepare-wsl-prebuilt.ps1" + } } Write-Host '' diff --git a/libs/hexagent_demo/electron/scripts/prepare-wsl-offline-assets.ps1 b/libs/hexagent_demo/electron/scripts/prepare-wsl-offline-assets.ps1 new file mode 100644 index 00000000..1346a101 --- /dev/null +++ b/libs/hexagent_demo/electron/scripts/prepare-wsl-offline-assets.ps1 @@ -0,0 +1,127 @@ +$ErrorActionPreference = "Stop" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ElectronDir = Resolve-Path "$ScriptDir\.." +$OfflineDir = Join-Path $ElectronDir "resources\wsl" + +$WslMsiName = "wsl.2.6.3.0.x64.msi" +$UbuntuRootfsName = "ubuntu-base-24.04-amd64.tar.gz" +$UseCnMirrors = ($env:HEXAGENT_USE_CN_MIRRORS -ne "0") + +if ($env:OS -ne "Windows_NT") { + Write-Host "Skipping offline WSL asset preparation: non-Windows environment." + exit 0 +} + +New-Item -ItemType Directory -Force -Path $OfflineDir | Out-Null + +function Ensure-DownloadedFile { + param( + [Parameter(Mandatory = $true)][string]$Name, + [Parameter(Mandatory = $true)][string[]]$Urls, + [long]$MinBytes = 1024, + [long]$MaxBytes = 0, + [string]$Kind = "generic" + ) + + $target = Join-Path $OfflineDir $Name + function Test-AssetValidity { + param( + [Parameter(Mandatory = $true)][string]$Path, + [Parameter(Mandatory = $true)][string]$AssetKind, + [long]$AssetMinBytes = 1024, + [long]$AssetMaxBytes = 0 + ) + if (-not (Test-Path $Path)) { return $false } + $item = Get-Item $Path -ErrorAction SilentlyContinue + if (-not $item) { return $false } + if ($item.Length -lt $AssetMinBytes) { return $false } + if ($AssetMaxBytes -gt 0 -and $item.Length -gt $AssetMaxBytes) { return $false } + + try { + $fs = [System.IO.File]::OpenRead($Path) + try { + $header = New-Object byte[] 4 + [void]$fs.Read($header, 0, 4) + } finally { + $fs.Dispose() + } + + if ($AssetKind -eq "msi") { + # MSI is a CFB container: D0 CF 11 E0 + return ($header[0] -eq 0xD0 -and $header[1] -eq 0xCF -and $header[2] -eq 0x11 -and $header[3] -eq 0xE0) + } + if ($AssetKind -eq "tar_gz") { + # Gzip magic: 1F 8B + return ($header[0] -eq 0x1F -and $header[1] -eq 0x8B) + } + return $true + } catch { + return $false + } + } + + if (Test-Path $target) { + if (Test-AssetValidity -Path $target -AssetKind $Kind -AssetMinBytes $MinBytes -AssetMaxBytes $MaxBytes) { + $sizeMb = [math]::Round(((Get-Item $target).Length / 1MB), 1) + Write-Host "==> Reusing cached offline asset: $Name (${sizeMb} MB)" + return + } + Write-Host "==> Cached file is invalid, redownloading: $Name" + Remove-Item -Force $target + } + + $lastError = $null + foreach ($url in $Urls) { + if (-not $url) { continue } + Write-Host "==> Downloading $Name from $url ..." + try { + Invoke-WebRequest -Uri $url -OutFile $target + if (Test-AssetValidity -Path $target -AssetKind $Kind -AssetMinBytes $MinBytes -AssetMaxBytes $MaxBytes) { + $sizeMb = [math]::Round(((Get-Item $target).Length / 1MB), 1) + Write-Host "==> Ready: $Name (${sizeMb} MB)" + return + } + Write-Host "==> Downloaded file failed validation, trying next mirror..." + if (Test-Path $target) { Remove-Item -Force $target } + } catch { + $lastError = $_ + Write-Host "==> Download failed from $url, trying next mirror..." + if (Test-Path $target) { Remove-Item -Force $target } + } + } + + if (-not (Test-Path $target)) { + if ($lastError) { + throw $lastError + } + throw "All download URLs failed for $Name" + } +} + +$wslMsiUrls = @() +$rootfsUrls = @() + +if ($env:HEXAGENT_WSL_MSI_URL) { + $wslMsiUrls += $env:HEXAGENT_WSL_MSI_URL +} +if ($env:HEXAGENT_UBUNTU_ROOTFS_URL) { + $rootfsUrls += $env:HEXAGENT_UBUNTU_ROOTFS_URL +} + +if ($UseCnMirrors) { + # Optional acceleration mirror for GitHub download. + $wslMsiUrls += "https://gh.llkk.cc/https://github.com/microsoft/WSL/releases/download/2.6.3/$WslMsiName" + # Smaller Ubuntu base rootfs (~28MB) for offline package size control. + $rootfsUrls += "https://mirrors.ustc.edu.cn/ubuntu-cdimage/ubuntu-base/releases/24.04/release/ubuntu-base-24.04.4-base-amd64.tar.gz" + $rootfsUrls += "https://mirror.sjtu.edu.cn/ubuntu-cdimage/ubuntu-base/releases/24.04/release/ubuntu-base-24.04.4-base-amd64.tar.gz" +} + +# Official fallback URLs. +$wslMsiUrls += "https://github.com/microsoft/WSL/releases/download/2.6.3/$WslMsiName" +$rootfsUrls += "https://cdimage.ubuntu.com/ubuntu-base/releases/24.04/release/ubuntu-base-24.04.4-base-amd64.tar.gz" + +Ensure-DownloadedFile -Name $WslMsiName -Urls $wslMsiUrls -Kind "msi" -MinBytes 10485760 +Ensure-DownloadedFile -Name $UbuntuRootfsName -Urls $rootfsUrls -Kind "tar_gz" -MinBytes 20971520 -MaxBytes 83886080 + +Write-Host "==> Offline WSL assets are ready in: $OfflineDir" diff --git a/libs/hexagent_demo/electron/wsl.2.6.3.0.x64.msi b/libs/hexagent_demo/electron/wsl.2.6.3.0.x64.msi new file mode 100644 index 00000000..3a090704 --- /dev/null +++ b/libs/hexagent_demo/electron/wsl.2.6.3.0.x64.msi @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:562c79aba6ce9b6e9170f069d31e3717f10d76dd8bfbee39b07eae0ca4a02ca0 +size 247123968 From d4ede0c024d71460c1435b51b0497f9c12e03aa0 Mon Sep 17 00:00:00 2001 From: xuelin-cell Date: Sat, 28 Mar 2026 08:04:30 +0800 Subject: [PATCH 24/28] fix(build): reuse hexagent-prebuilt tar during offline packaging --- .../electron/scripts/prepare-wsl-prebuilt.ps1 | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/libs/hexagent_demo/electron/scripts/prepare-wsl-prebuilt.ps1 b/libs/hexagent_demo/electron/scripts/prepare-wsl-prebuilt.ps1 index 33396e6e..011d7395 100644 --- a/libs/hexagent_demo/electron/scripts/prepare-wsl-prebuilt.ps1 +++ b/libs/hexagent_demo/electron/scripts/prepare-wsl-prebuilt.ps1 @@ -3,14 +3,29 @@ $ErrorActionPreference = "Stop" $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $HexagentRoot = Resolve-Path "$ScriptDir\..\..\.." $PrebuiltDir = Join-Path $HexagentRoot "hexagent\sandbox\vm\wsl\prebuilt" -$PrebuiltTar = Join-Path $PrebuiltDir "openagent-prebuilt.tar" -$DistroName = "openagent" +$PrebuiltTar = Join-Path $PrebuiltDir "hexagent-prebuilt.tar" +$LegacyPrebuiltTar = Join-Path $PrebuiltDir "openagent-prebuilt.tar" +$DistroName = if ($env:HEXAGENT_WSL_DISTRO) { $env:HEXAGENT_WSL_DISTRO } else { "hexagent" } +$ForceRebuild = ($env:HEXAGENT_FORCE_REBUILD_WSL_PREBUILT -eq "1") if ($env:OS -ne "Windows_NT") { Write-Host "Skipping WSL prebuilt export: non-Windows environment." exit 0 } +New-Item -ItemType Directory -Force -Path $PrebuiltDir | Out-Null + +if ((-not (Test-Path $PrebuiltTar)) -and (Test-Path $LegacyPrebuiltTar)) { + Write-Host "==> Found legacy prebuilt tar name, renaming to hexagent-prebuilt.tar ..." + Move-Item -Force $LegacyPrebuiltTar $PrebuiltTar +} + +if ((Test-Path $PrebuiltTar) -and (-not $ForceRebuild)) { + $sizeMb = [math]::Round(((Get-Item $PrebuiltTar).Length / 1MB), 1) + Write-Host "==> Reusing existing WSL prebuilt image: $PrebuiltTar (${sizeMb} MB)" + exit 0 +} + if (-not (Get-Command wsl -ErrorAction SilentlyContinue)) { throw "wsl command not found. Install WSL first." } @@ -18,10 +33,9 @@ if (-not (Get-Command wsl -ErrorAction SilentlyContinue)) { Write-Host "==> Ensuring distro '$DistroName' can start..." & wsl -d $DistroName -- echo ok | Out-Null if ($LASTEXITCODE -ne 0) { - throw "WSL distro '$DistroName' is not available/runnable. Please initialize VM Instance first." + throw "WSL distro '$DistroName' is not available/runnable. Please initialize VM Instance first, or provide an existing hexagent-prebuilt.tar." } -New-Item -ItemType Directory -Force -Path $PrebuiltDir | Out-Null if (Test-Path $PrebuiltTar) { Remove-Item -Force $PrebuiltTar } From a3e0ad5cb9c88dbd05df7a995a8cefb1fe7e2696 Mon Sep 17 00:00:00 2001 From: xuelin-cell Date: Sat, 28 Mar 2026 08:26:41 +0800 Subject: [PATCH 25/28] chore(branding): rename desktop app to ClawWork and update Windows icon --- libs/hexagent_demo/electron/package.json | 4 ++-- .../hexagent_demo/electron/resources/icon.ico | Bin 36624 -> 23006 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/hexagent_demo/electron/package.json b/libs/hexagent_demo/electron/package.json index 2cd16416..d03b6f53 100644 --- a/libs/hexagent_demo/electron/package.json +++ b/libs/hexagent_demo/electron/package.json @@ -1,7 +1,7 @@ { "name": "hexagent", "version": "0.0.1", - "description": "HexAgent Desktop App", + "description": "ClawWork Desktop App", "main": "main.js", "scripts": { "dev": "ELECTRON_DEV=1 electron .", @@ -22,7 +22,7 @@ }, "build": { "appId": "com.hexagent.app", - "productName": "HexAgent", + "productName": "ClawWork", "directories": { "buildResources": "resources", "output": "dist" diff --git a/libs/hexagent_demo/electron/resources/icon.ico b/libs/hexagent_demo/electron/resources/icon.ico index af153efbeef697b1cbd8a283328a7ca6c970ae6f..0caa30a6251e4898f5ef9d22809d3a50c0b03b81 100644 GIT binary patch literal 23006 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 literal 36624 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

$? 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 From 4c483a59c04ab6e83edf0b4ffab86bc529934a7a Mon Sep 17 00:00:00 2001 From: xuelin-cell Date: Sat, 28 Mar 2026 08:49:39 +0800 Subject: [PATCH 26/28] fix(branding): point builder icon path to buildResources and regenerate ICO --- libs/hexagent_demo/electron/package.json | 4 ++-- .../hexagent_demo/electron/resources/icon.ico | Bin 23006 -> 52011 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/hexagent_demo/electron/package.json b/libs/hexagent_demo/electron/package.json index d03b6f53..fb679f00 100644 --- a/libs/hexagent_demo/electron/package.json +++ b/libs/hexagent_demo/electron/package.json @@ -57,7 +57,7 @@ ] } ], - "icon": "resources/icon.icns", + "icon": "icon.icns", "category": "public.app-category.developer-tools", "identity": null, "signIgnore": [] @@ -78,7 +78,7 @@ ] } ], - "icon": "resources/icon.ico", + "icon": "icon.ico", "signAndEditExecutable": false }, "nsis": { diff --git a/libs/hexagent_demo/electron/resources/icon.ico b/libs/hexagent_demo/electron/resources/icon.ico index 0caa30a6251e4898f5ef9d22809d3a50c0b03b81..2b3b0647ed5a4b304ecae53a1895413201d1110a 100644 GIT binary patch literal 52011 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⩔7#$43+`=M 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 From 089bd4ad276c9a360a45b30302d0dd6ec1c1c08d Mon Sep 17 00:00:00 2001 From: xuelin-cell Date: Sat, 28 Mar 2026 09:05:22 +0800 Subject: [PATCH 27/28] fix(windows-branding): force ClawWork icon for desktop shortcut and window --- libs/hexagent_demo/electron/main.js | 5 +++++ libs/hexagent_demo/electron/package.json | 4 ++++ libs/hexagent_demo/electron/resources/installer.nsh | 6 ++++++ 3 files changed, 15 insertions(+) diff --git a/libs/hexagent_demo/electron/main.js b/libs/hexagent_demo/electron/main.js index dd5191e6..c31b4f6a 100644 --- a/libs/hexagent_demo/electron/main.js +++ b/libs/hexagent_demo/electron/main.js @@ -419,9 +419,14 @@ try { // ── Window ─────────────────────────────────────────────────────────────────── function createWindow() { + const winIconPath = IS_DEV + ? path.join(__dirname, "resources", "icon.ico") + : path.join(process.resourcesPath, "app-icon.ico"); + mainWindow = new BrowserWindow({ width: 1200, height: 800, + icon: fs.existsSync(winIconPath) ? winIconPath : undefined, webPreferences: { preload: path.join(__dirname, "preload.js"), contextIsolation: true, diff --git a/libs/hexagent_demo/electron/package.json b/libs/hexagent_demo/electron/package.json index fb679f00..322157a4 100644 --- a/libs/hexagent_demo/electron/package.json +++ b/libs/hexagent_demo/electron/package.json @@ -68,6 +68,10 @@ { "from": "resources/wsl/", "to": "wsl" + }, + { + "from": "resources/icon.ico", + "to": "app-icon.ico" } ], "target": [ diff --git a/libs/hexagent_demo/electron/resources/installer.nsh b/libs/hexagent_demo/electron/resources/installer.nsh index 7609d096..6a228a6f 100644 --- a/libs/hexagent_demo/electron/resources/installer.nsh +++ b/libs/hexagent_demo/electron/resources/installer.nsh @@ -1,3 +1,9 @@ +!macro customInstall + ; Force desktop shortcut to use bundled ClawWork icon, independent of EXE icon resource. + Delete "$DESKTOP\ClawWork.lnk" + CreateShortCut "$DESKTOP\ClawWork.lnk" "$INSTDIR\ClawWork.exe" "" "$INSTDIR\resources\app-icon.ico" 0 +!macroend + !macro customUnInstall RMDir /r "$PROFILE\.hexagent" !macroend From 9137f499042b62c91b02f332c66e911b308979c3 Mon Sep 17 00:00:00 2001 From: xuelin-cell Date: Sat, 28 Mar 2026 09:59:54 +0800 Subject: [PATCH 28/28] style(lint): apply ruff formatting for environment probe changes --- libs/hexagent/hexagent/harness/environment.py | 5 ++--- libs/hexagent/tests/unit_tests/harness/test_environment.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/libs/hexagent/hexagent/harness/environment.py b/libs/hexagent/hexagent/harness/environment.py index 7d6fbc6e..b6f6f578 100644 --- a/libs/hexagent/hexagent/harness/environment.py +++ b/libs/hexagent/hexagent/harness/environment.py @@ -75,8 +75,7 @@ async def _probe_datetime(self) -> datetime: logger.warning("Unparseable python datetime probe: %r", py_raw) logger.warning( - "Environment datetime probes failed; falling back to UTC now. " - "date.stdout=%r date.stderr=%r python3.stdout=%r python3.stderr=%r", + "Environment datetime probes failed; falling back to UTC now. date.stdout=%r date.stderr=%r python3.stdout=%r python3.stderr=%r", probe.stdout, probe.stderr, py_probe.stdout, @@ -100,7 +99,7 @@ async def resolve(self) -> EnvironmentContext: f"printf '%s\\n' {qd}; " "uname -s | tr '[:upper:]' '[:lower:]'; " f"printf '%s\\n' {qd}; " - "basename \"${SHELL:-bash}\"; " + 'basename "${SHELL:-bash}"; ' f"printf '%s\\n' {qd}; " "uname -sr; " f"printf '%s\\n' {qd}; " diff --git a/libs/hexagent/tests/unit_tests/harness/test_environment.py b/libs/hexagent/tests/unit_tests/harness/test_environment.py index e41f9b32..f93ac826 100644 --- a/libs/hexagent/tests/unit_tests/harness/test_environment.py +++ b/libs/hexagent/tests/unit_tests/harness/test_environment.py @@ -1,4 +1,4 @@ -# ruff: noqa: PLR2004 +# ruff: noqa: PLR2004 """Tests for EnvironmentResolver.""" from __future__ import annotations