diff --git a/.github/workflows/deploy-site.yml b/.github/workflows/deploy-site.yml index df0a42d51d..7f6c4b5ad9 100644 --- a/.github/workflows/deploy-site.yml +++ b/.github/workflows/deploy-site.yml @@ -75,7 +75,7 @@ jobs: - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: - python-version: '3.11' + python-version: '3.14' - name: Install PyYAML for skill extraction uses: ./.github/actions/retry diff --git a/.github/workflows/desktop-bundled-release.yml b/.github/workflows/desktop-bundled-release.yml index 3b033ff4e2..2ad31d4b4b 100644 --- a/.github/workflows/desktop-bundled-release.yml +++ b/.github/workflows/desktop-bundled-release.yml @@ -430,12 +430,12 @@ jobs: AZURE_TENANT_ID: ${{ vars.AZURE_TENANT_ID }} AZURE_TOKEN_CREDENTIALS: prod run: | - uv run --no-project --python 3.11 python scripts/bundles/desktop.py --tag="$HERMES_PAYLOAD_TAG" --variant=bundled + uv run --no-project --python 3.14 python scripts/bundles/desktop.py --tag="$HERMES_PAYLOAD_TAG" --variant=bundled # The Store-submission MSIX is the same bundled payload re-packed # with the Partner Center packaging identity (publish-win32-store # bundles these into the universal Store .msixbundle and submits it; # they also land in the tag archive, never a feed dir). - uv run --no-project --python 3.11 python scripts/bundles/desktop.py --tag="$HERMES_PAYLOAD_TAG" --variant=store + uv run --no-project --python 3.14 python scripts/bundles/desktop.py --tag="$HERMES_PAYLOAD_TAG" --variant=store - name: Verify native signature cache contracts shell: bash @@ -730,7 +730,7 @@ jobs: # every Mach-O) — raise the fd limit and let DEBUG show progress. ulimit -n 16384 2>/dev/null || true echo "file descriptor limit: soft=$(ulimit -Sn) hard=$(ulimit -Hn)" - uv run --no-project --python 3.11 python scripts/bundles/desktop.py --tag="$HERMES_PAYLOAD_TAG" --variant=bundled + uv run --no-project --python 3.14 python scripts/bundles/desktop.py --tag="$HERMES_PAYLOAD_TAG" --variant=bundled - name: Audit bundle architecture shell: bash diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 2aca250939..57fa09fb96 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -150,10 +150,10 @@ jobs: # fail the job (2026-07-28 incident). Keep in sync with tests.yml. version: "0.9.28" - - name: Set up Python 3.11 (for docker tests) + - name: Set up Python 3.14 (for docker tests) uses: ./.github/actions/retry with: - command: uv python install 3.11 + command: uv python install 3.14 - name: Install Python dependencies (for docker tests) # ``dev`` extra pulls in pytest, pytest-asyncio — @@ -162,7 +162,7 @@ jobs: # subprocess and don't import hermes_agent's optional deps. uses: ./.github/actions/retry with: - command: uv sync --locked --python 3.11 --extra dev + command: uv sync --locked --python 3.14 --extra dev - name: Run docker integration tests env: diff --git a/.github/workflows/docs-site-checks.yml b/.github/workflows/docs-site-checks.yml index f33af0fc1a..c37f119a82 100644 --- a/.github/workflows/docs-site-checks.yml +++ b/.github/workflows/docs-site-checks.yml @@ -31,7 +31,7 @@ jobs: - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: - python-version: "3.11" + python-version: "3.14" - name: Install ascii-guard uses: ./.github/actions/retry diff --git a/.github/workflows/e2e-desktop.yml b/.github/workflows/e2e-desktop.yml index 9c974a56ec..dc772cdaac 100644 --- a/.github/workflows/e2e-desktop.yml +++ b/.github/workflows/e2e-desktop.yml @@ -70,15 +70,15 @@ jobs: pyproject.toml uv.lock - - name: Set up Python 3.11 + - name: Set up Python 3.14 uses: ./.github/actions/retry with: - command: uv python install 3.11 + command: uv python install 3.14 - name: Install Python dependencies uses: ./.github/actions/retry with: - command: uv sync --locked --python 3.11 --extra all --extra dev + command: uv sync --locked --python 3.14 --extra all --extra dev # ── Build desktop app ───────────────────────────────────────────── # The Playwright step below runs `npm run build` before testing so diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6b4023ebe5..7a50e3d145 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -171,10 +171,10 @@ jobs: # fail the job (2026-07-28 incident). Keep in sync with tests.yml. version: "0.9.28" - - name: Set up Python 3.11 + - name: Set up Python 3.14 uses: ./.github/actions/retry with: - command: uv python install 3.11 + command: uv python install 3.14 - name: Run footgun checker run: python scripts/check-windows-footguns.py --all diff --git a/.github/workflows/skills-index.yml b/.github/workflows/skills-index.yml index cf6812630e..c15dc8072b 100644 --- a/.github/workflows/skills-index.yml +++ b/.github/workflows/skills-index.yml @@ -34,7 +34,7 @@ jobs: - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: - python-version: "3.11" + python-version: "3.14" - name: Install dependencies uses: ./.github/actions/retry diff --git a/.github/workflows/termux-verify.yml b/.github/workflows/termux-verify.yml index 6830ba0bb8..1219015f69 100644 --- a/.github/workflows/termux-verify.yml +++ b/.github/workflows/termux-verify.yml @@ -35,7 +35,7 @@ jobs: run: | sudo apt-get update sudo apt-get install -y patchelf - uv venv --python 3.11 .venv + uv venv --python 3.14 .venv uv export --frozen --extra dev --no-emit-project --no-hashes -o "$RUNNER_TEMP/requirements.txt" uv pip install --python .venv/bin/python -r "$RUNNER_TEMP/requirements.txt" - name: Run the native linker and wheel contracts diff --git a/.github/workflows/tests-os.yml b/.github/workflows/tests-os.yml index 0dd8812ffa..14c7b9c7a8 100644 --- a/.github/workflows/tests-os.yml +++ b/.github/workflows/tests-os.yml @@ -80,10 +80,10 @@ jobs: pyproject.toml uv.lock - - name: Set up Python 3.11 + - name: Set up Python 3.14 uses: ./.github/actions/retry with: - command: uv python install 3.11 + command: uv python install 3.14 - name: Install dependencies # Same extras as the Linux test lane so an OS-marked test can import @@ -93,7 +93,7 @@ jobs: # because it could not build here). uses: ./.github/actions/retry with: - command: uv sync --locked --python 3.11 --extra all --extra dev --extra anthropic --extra mistral --extra fal --extra modal --extra daytona --extra hindsight --extra parallel-web + command: uv sync --locked --python 3.14 --extra all --extra dev --extra anthropic --extra mistral --extra fal --extra modal --extra daytona --extra hindsight --extra parallel-web - name: Minimize uv cache run: uv cache prune --ci diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a0dca54ad0..4193a7eff7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -59,10 +59,10 @@ jobs: pyproject.toml uv.lock - - name: Set up Python 3.11 + - name: Set up Python 3.14 uses: ./.github/actions/retry with: - command: uv python install 3.11 + command: uv python install 3.14 - name: Install dependencies # `uv sync --locked` installs the exact pinned set from uv.lock (and @@ -80,7 +80,7 @@ jobs: # also honors the exact supply-chain pins these extras carry. uses: ./.github/actions/retry with: - command: uv sync --locked --python 3.11 --extra all --extra dev --extra anthropic --extra mistral --extra fal --extra modal --extra daytona --extra hindsight --extra parallel-web + command: uv sync --locked --python 3.14 --extra all --extra dev --extra anthropic --extra mistral --extra fal --extra modal --extra daytona --extra hindsight --extra parallel-web - name: Minimize uv cache # Optimized for CI: prunes pre-built wheels that are cheap to @@ -164,8 +164,8 @@ jobs: pyproject.toml uv.lock - - name: Set up Python 3.11 - run: uv python install 3.11 + - name: Set up Python 3.14 + run: uv python install 3.14 - name: Install dependencies # `uv sync --locked` installs the exact pinned set from uv.lock (and @@ -179,7 +179,7 @@ jobs: # in the venv up front. uses: ./.github/actions/retry with: - command: uv sync --locked --python 3.11 --extra all --extra dev --extra anthropic --extra mistral --extra fal --extra modal --extra daytona --extra hindsight --extra parallel-web + command: uv sync --locked --python 3.14 --extra all --extra dev --extra anthropic --extra mistral --extra fal --extra modal --extra daytona --extra hindsight --extra parallel-web - name: Minimize uv cache # Optimized for CI: prunes pre-built wheels that are cheap to diff --git a/.github/workflows/windows-venv-e2e.yml b/.github/workflows/windows-venv-e2e.yml index a09b2f2d45..c8c8f84fd7 100644 --- a/.github/workflows/windows-venv-e2e.yml +++ b/.github/workflows/windows-venv-e2e.yml @@ -42,15 +42,15 @@ jobs: pyproject.toml uv.lock - - name: Set up Python 3.11 + - name: Set up Python 3.14 uses: ./.github/actions/retry with: - command: uv python install 3.11 + command: uv python install 3.14 - name: Install dependencies uses: ./.github/actions/retry with: - command: uv sync --locked --python 3.11 --extra dev --extra messaging + command: uv sync --locked --python 3.14 --extra dev --extra messaging - name: Run venv-holder live E2E shell: bash diff --git a/README.md b/README.md index 57d3f91629..b256850777 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Run this in PowerShell: iex (irm https://hermes-agent.nousresearch.com/install.ps1) ``` -The installer handles everything: uv, Python 3.11, Node.js, ripgrep, ffmpeg, **and a portable Git Bash** (MinGit, unpacked to `%LOCALAPPDATA%\hermes\git` — no admin required, completely isolated from any system Git install). Hermes uses this bundled Git Bash to run shell commands. +The installer handles everything: uv, Python 3.14, Node.js, ripgrep, ffmpeg, **and a portable Git Bash** (MinGit, unpacked to `%LOCALAPPDATA%\hermes\git` — no admin required, completely isolated from any system Git install). Hermes uses this bundled Git Bash to run shell commands. If you already have Git installed, the installer detects it and uses that instead. Otherwise a ~45MB MinGit download is all you need — it won't touch or interfere with any system Git. @@ -239,7 +239,7 @@ against its own checkout, destroying the running runtime mid-session. ```bash curl -LsSf https://astral.sh/uv/install.sh | sh -uv venv ~/.hermes/venvs/hermes-dev --python 3.11 +uv venv ~/.hermes/venvs/hermes-dev --python 3.14 source ~/.hermes/venvs/hermes-dev/bin/activate uv pip install -e ".[all,dev]" scripts/run_tests.sh diff --git a/hermes_cli/config.py b/hermes_cli/config.py index 69b15427d4..47573232e1 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -2849,7 +2849,7 @@ def _show_terminal_section(config: Dict[str, Any]) -> None: print(f" Timeout: {terminal.get('timeout', 60)}s") configured = lambda *names: 'configured' if all(get_env_value(n) for n in names) else '(not set)' # noqa: E731 - default_img = 'nikolaik/python-nodejs:python3.11-nodejs20' + default_img = 'nikolaik/python-nodejs:python3.14-nodejs22' backend_lines = { 'docker': lambda: [f" Docker image: {terminal.get('docker_image', default_img)}"], 'singularity': lambda: [f" Image: {terminal.get('singularity_image', 'docker://' + default_img)}"], diff --git a/hermes_cli/config_defaults.py b/hermes_cli/config_defaults.py index 60df449de9..5337cd4638 100644 --- a/hermes_cli/config_defaults.py +++ b/hermes_cli/config_defaults.py @@ -301,15 +301,15 @@ DEFAULT_CONFIG = { # go first because n/nvm/asdf write PATH exports there without an interactivity guard. Turn # off if an rc file misbehaves when sourced non-interactively (exits on TTY check). "auto_source_bashrc": True, - "docker_image": "nikolaik/python-nodejs:python3.11-nodejs20", + "docker_image": "nikolaik/python-nodejs:python3.14-nodejs22", "docker_forward_env": [], # Exact key-value env pairs set inside Docker containers (unlike docker_forward_env, which # reads host values) — useful under systemd without the user's shell env. Example: # {"SSH_AUTH_SOCK": "/run/user/1000/ssh-agent.sock"} "docker_env": {}, - "singularity_image": "docker://nikolaik/python-nodejs:python3.11-nodejs20", - "modal_image": "nikolaik/python-nodejs:python3.11-nodejs20", - "daytona_image": "nikolaik/python-nodejs:python3.11-nodejs20", + "singularity_image": "docker://nikolaik/python-nodejs:python3.14-nodejs22", + "modal_image": "nikolaik/python-nodejs:python3.14-nodejs22", + "daytona_image": "nikolaik/python-nodejs:python3.14-nodejs22", "vercel_runtime": "node24", # vercel_sandbox backend only: node24 | node22 | python3.13 # Container limits (docker, singularity, modal, daytona, vercel_sandbox; not local/ssh). "container_cpu": 1, diff --git a/hermes_cli/runtime_repair.py b/hermes_cli/runtime_repair.py index 4c8d547200..1ac3081358 100644 --- a/hermes_cli/runtime_repair.py +++ b/hermes_cli/runtime_repair.py @@ -530,14 +530,14 @@ def _install_safe_python_generation( return result # All patches on the current minor line are vulnerable or rejected. - # Fall forward to the next supported minor (e.g. 3.11 → 3.12) so the - # user isn't stuck on every `hermes update` with no path to a fixed - # runtime (issue #76106). The requires-python constraint - # (>=3.11,<3.14) and the downstream import smoke-test gate + # Fall forward to the next supported minor (e.g. 3.14 → 3.15 when the + # ceiling rises) so the user isn't stuck on every `hermes update` with + # no path to a fixed runtime (issue #76106). The requires-python + # constraint (>=3.14,<3.15) and the downstream import smoke-test gate # compatibility; we only need to stay inside that window. cur_major, cur_minor = current.python_version[:2] fb_tried: set[tuple[int, int, int]] = set(tried_versions) - for next_minor in range(cur_minor + 1, 14): # up to 3.13 + for next_minor in range(cur_minor + 1, 15): # up to 3.14 (<3.15 ceiling) next_request = f"{cur_major}.{next_minor}" print( f" → No fixed {cur_major}.{cur_minor} build available; " @@ -1182,5 +1182,5 @@ def repair_vulnerable_runtime( # --------------------------------------------------------------------------- -def rebuild_venv(uv_bin: str, venv_dir: Path, python_version: str = "3.11") -> bool: +def rebuild_venv(uv_bin: str, venv_dir: Path, python_version: str = "3.14") -> bool: return True # dont remove me. ask ethernet diff --git a/pm/lock.json b/pm/lock.json index b2ad24da9c..fd014a40eb 100644 --- a/pm/lock.json +++ b/pm/lock.json @@ -300,35 +300,35 @@ "python": { "artifacts": { "darwin-arm64": { - "sha256": "fcba9f3f676c83e07225e38116649f0c6eb94cb4fcc166632cf92769462b6e39", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.11.16+20260814-aarch64-apple-darwin-install_only.tar.gz" + "sha256": "30daa970c7d223530120f1693cd3c6fa4c0c0d31ef158710b0dd77f286a5b23e", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20260901/cpython-3.14.7+20260901-aarch64-apple-darwin-install_only.tar.gz" }, "darwin-x64": { - "sha256": "d9117d31251ba5c9a81ac7ad7c00dab1d5228e261eb0cda94550b4da1cc73bd1", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.11.16+20260814-x86_64-apple-darwin-install_only.tar.gz" + "sha256": "dd8841a2e8ef94bd1a02b52f92843120942140f112145d4e0199abab56f120b1", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20260901/cpython-3.14.7+20260901-x86_64-apple-darwin-install_only.tar.gz" }, "linux-arm64": { - "sha256": "9142c12dd3559eaac58a18d8905b99a293979d5e5a1b4fb5cf4cebbf82ef6f33", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.11.16+20260814-aarch64-unknown-linux-gnu-install_only.tar.gz" + "sha256": "30f1cc489be654477d895b441e196bb080738bf0456da82080ad4ab66a22d80f", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20260901/cpython-3.14.7+20260901-aarch64-unknown-linux-gnu-install_only.tar.gz" }, "linux-arm64-bionic": { - "sha256": "61a8df1dfee53de364757aa751faac033725262a4be04f086c3e3fedbe38991d", - "url": "https://tur.kcubeterm.com/pool/tur/python3.11_3.11.16_aarch64.deb" + "sha256": "3166e56c2b6c03fff41191fbb9d736302978e7c484702814d9f6dc99dd6006bd", + "url": "https://packages.termux.dev/apt/termux-main/pool/main/p/python/python_3.14.6-1_aarch64.deb" }, "linux-x64": { - "sha256": "33994fad90145ba559ebbe8a18d69fa7e56653502f7ba14ba07199b52cde3775", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.11.16+20260814-x86_64-unknown-linux-gnu-install_only.tar.gz" + "sha256": "0ab3305457051cd3e7c031857e005f1bda17c218a1990567dacaaac6dd1d14f0", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20260901/cpython-3.14.7+20260901-x86_64-unknown-linux-gnu-install_only.tar.gz" }, "win32-arm64": { - "sha256": "450cbf91ff0dbfce7fa1d40ba19103187852076496b6153e528fa6dc43a88a58", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.11.16+20260814-aarch64-pc-windows-msvc-install_only.tar.gz" + "sha256": "5efa2548bf1248ca07ed6f8afb0cb52b83d4869d533ea5be919a989c8ee1b17c", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20260901/cpython-3.14.7+20260901-aarch64-pc-windows-msvc-install_only.tar.gz" }, "win32-x64": { - "sha256": "ffaee1e94c8488f833473e56e1c980ca1a58d91126d21ddf0f6ba052e69cf511", - "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20260814/cpython-3.11.16+20260814-x86_64-pc-windows-msvc-install_only.tar.gz" + "sha256": "5d9242012dded591d723a3a572dda265173ad58d8a3e6fdbc6dac8f94f36c80a", + "url": "https://github.com/astral-sh/python-build-standalone/releases/download/20260901/cpython-3.14.7+20260901-x86_64-pc-windows-msvc-install_only.tar.gz" } }, - "version": "3.11.16+20260814" + "version": "3.14.7+20260901" }, "ripgrep": { "artifacts": { diff --git a/pm/packages.py b/pm/packages.py index 0223e1815c..c801afc6ba 100644 --- a/pm/packages.py +++ b/pm/packages.py @@ -239,12 +239,20 @@ class Python(_BionicDebArm, BinaryPackage, DebPackage): probe_version = False binary_rel = {"win32": "python.exe", "posix": "bin/python3"} # The staged .deb's main binary: DebPackage.verify checks it. - main_bin_rel = "bin/python3.11" + main_bin_rel = "bin/python3.14" def main_rel(self, target: str) -> str: return self.main_bin_rel - deb_package = "python3.11" + # termux-main (official termux repo) python deb. It lags python-build- + # standalone by one patch (3.14.6-1 vs 3.14.7), so the bionic row is a + # manual pin -- never derived from the node version, and pm update leaves + # it alone (no bionic resolver). + deb_package = "python" + _BIONIC_URL = ( + "https://packages.termux.dev/apt/termux-main/pool/main/p/python/" + "python_3.14.6-1_aarch64.deb" + ) def stage(self, store: Store, staged: Path, version: str, target: str) -> None: super().stage(store, staged, version, target) @@ -261,9 +269,8 @@ class Python(_BionicDebArm, BinaryPackage, DebPackage): def fetch_url(self, version: str, target: str) -> str: if target == "linux-arm64-bionic": - pyver = version.partition("+")[0] - return f"https://tur.kcubeterm.com/pool/tur/python3.11_{pyver}_aarch64.deb" - # lock version is "+", e.g. "3.11.13+202****0807" + return self._BIONIC_URL + # lock version is "+", e.g. "3.14.7+20260901" pyver, _, tag = version.partition("+") if not tag: raise InstallError(self.name, f"version {version!r} needs the + tag") @@ -274,10 +281,11 @@ class Python(_BionicDebArm, BinaryPackage, DebPackage): ) def latest_versions(self, target: str, locked=None) -> list[str]: - # Stay on the locked python minor line (3.11); bump only the + # Stay on the locked python minor line (3.14); bump only the # +. A major/minor bump is a deliberate decision, never - # an auto-update. - if not locked or "+" not in locked: + # an auto-update. The bionic row is a manual termux-main pin -- no + # python-build-standalone build exists for it, so leave it locked. + if target == "linux-arm64-bionic" or not locked or "+" not in locked: return [] pyver = locked.partition("+")[0] minor = ".".join(pyver.split(".")[:2]) diff --git a/pm/update.py b/pm/update.py index 787d99a89a..f7008193fe 100644 --- a/pm/update.py +++ b/pm/update.py @@ -43,7 +43,7 @@ _VERSION_PART_RE = re.compile(r"\d+|[A-Za-z]+") def version_key(version: str) -> tuple: """A sortable key for a version string. Handles dot-separated numerics, - +suffixes (python's 3.11.16+20260814, git's 2.53.0+3), and plain build + +suffixes (python's 3.14.7+20260901, git's 2.53.0+3), and plain build numbers (llamacpp's 10362). Non-numeric segments sort after numerics so a prerelease never beats its release.""" key = [] diff --git a/scripts/bundles/mint_launchers.py b/scripts/bundles/mint_launchers.py index 8ce7a96ac8..f19989b822 100644 --- a/scripts/bundles/mint_launchers.py +++ b/scripts/bundles/mint_launchers.py @@ -72,7 +72,7 @@ def make_maker(bin_dir, shebang_python, wrapper_text): def mint_one(bin_dir, shebang_python, wrapper_text, spec): """Mint one launcher exe; returns its absolute path. distlib also - writes a python-versioned twin (hermes-3.11.exe) — the payload ships + writes a python-versioned twin (hermes-3.14.exe) — the payload ships exactly one name per entry, so the twin is removed.""" maker = make_maker(bin_dir, shebang_python, wrapper_text) filenames = maker.make(f"{spec['name']} = {spec['module']}:{spec['func']}") diff --git a/scripts/ci/test_install_ps1_cli_launchers.ps1 b/scripts/ci/test_install_ps1_cli_launchers.ps1 index d5b0143e4c..9bf222e332 100644 --- a/scripts/ci/test_install_ps1_cli_launchers.ps1 +++ b/scripts/ci/test_install_ps1_cli_launchers.ps1 @@ -115,7 +115,7 @@ try { Copy-Item (Join-Path $repoRoot 'hermes_constants.py') (Join-Path $installRoot 'hermes_constants.py') $store = Join-Path $caseRoot 'store' - $entryName = 'python-3.11.15+x20260807-win32-arm64' + $entryName = 'python-3.14.7+x20260901-win32-arm64' New-Item -ItemType Directory -Force -Path (Join-Path $store $entryName) | Out-Null Copy-Item $python (Join-Path $store "$entryName\python.exe") ('{"schema": 1, "packages": {"python": {"entry": "' + $entryName + '"}}}') | diff --git a/scripts/generate-icons.mjs b/scripts/generate-icons.mjs index 515b2714e4..58ba2efc83 100644 --- a/scripts/generate-icons.mjs +++ b/scripts/generate-icons.mjs @@ -24,7 +24,7 @@ export function generateIcons(args = [], { root = repoRoot, run = spawnSync, env delete childEnv.PYTHONPATH delete childEnv.PYTHONHOME const result = run('uv', [ - 'run', '--isolated', '--locked', '--only-group', 'icon-build', '--python', '3.11', + 'run', '--isolated', '--locked', '--only-group', 'icon-build', '--python', '3.14', // PM copies its wheel cache into payloads. Keep build wheels outside it. '--cache-dir', path.join(root, '.cache', 'icon-build'), 'python', path.join(root, 'scripts', 'generate_icons.py'), ...args diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 22e9f67457..b03ee8ca2a 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -505,7 +505,7 @@ function Invoke-BootstrapPm { $uv = Get-Uv $lock = Get-Content (Join-Path $InstallDir "pm\lock.json") -Raw | ConvertFrom-Json $pyPin = $lock.packages.python - $pyVersion = if ($pyPin) { ($pyPin.version -split '\+')[0] -replace '^(\d+\.\d+).*', '$1' } else { '3.11' } + $pyVersion = if ($pyPin) { ($pyPin.version -split '\+')[0] -replace '^(\d+\.\d+).*', '$1' } else { '3.14' } Log "delegating python + venv + tools to pm (hash-verified via uv.lock)" Push-Location $InstallDir try { diff --git a/scripts/install.sh b/scripts/install.sh index 00769e1508..f389c173c3 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -289,7 +289,7 @@ bootstrap_pm() { _py="$(awk '/^ "python": \{/ { in_py = 1 } in_py && /^ "version":/ { gsub(/.*: "|"$|",$/, ""); print; exit }' \ "$INSTALL_DIR/pm/lock.json" | cut -d+ -f1 | cut -d. -f1,2)" - [ -n "$_py" ] || _py="3.11" + [ -n "$_py" ] || _py="3.14" log "delegating python + venv + tools to pm (hash-verified via uv.lock)" (cd "$INSTALL_DIR" && "$UV_CMD" run --no-project --python "$_py" python -m pm.cli install) \ || fail "pm install failed" diff --git a/scripts/smoke-payload.sh b/scripts/smoke-payload.sh index 6a4c029fba..b096996103 100644 --- a/scripts/smoke-payload.sh +++ b/scripts/smoke-payload.sh @@ -14,7 +14,7 @@ PYTHON_ENTRY=$(node -e " " "$PWD") case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) PY="$PWD/tools/$PYTHON_ENTRY/python.exe"; SP="$PWD/venv/Lib/site-packages" ;; - *) PY="$PWD/tools/$PYTHON_ENTRY/bin/python3"; SP="$PWD/venv/lib/python3.11/site-packages" ;; + *) PY="$PWD/tools/$PYTHON_ENTRY/bin/python3"; SP="$PWD/venv/lib/python3.14/site-packages" ;; esac [ -f "$PY" ] || { echo "FAIL: no store interpreter at $PY"; exit 1; } diff --git a/scripts/termux/build_config.sh b/scripts/termux/build_config.sh index 8eb369e62c..ca6c888e96 100644 --- a/scripts/termux/build_config.sh +++ b/scripts/termux/build_config.sh @@ -12,9 +12,9 @@ PLATFORM_TAG="android_24_arm64_v8a" # ABI tag of the bundled interpreter, derived from the pm python version -# (cp311 for the 3.11.x line the lock pins). Kept beside the platform tag +# (cp314 for the 3.14.x line the lock pins). Kept beside the platform tag # because index.json's pythonAbi field must agree with it. -PYTHON_ABI="cp311" +PYTHON_ABI="cp314" # Toolchain pins for the psutil patched-local build path (--no-build- # isolation). Pure-python installs only: maturin is deliberately absent -- diff --git a/scripts/termux/build_deb.sh b/scripts/termux/build_deb.sh index 9bd39a2410..e947b44519 100644 --- a/scripts/termux/build_deb.sh +++ b/scripts/termux/build_deb.sh @@ -70,7 +70,7 @@ COMMIT="$(git -C "$REPO_ABS" rev-parse --verify "refs/tags/$TAG^{commit}")" \ for d in python node uv npm ffmpeg ripgrep runtime-libs app wheelhouse; do [ -d "$PAYLOAD_ABS/$d" ] || fail "payload missing $d/ -- run termux_build.sh + build_cpython.sh + build_node.sh first" done -PYBIN_REL="data/data/com.termux/files/usr/bin/python3.11" +PYBIN_REL="data/data/com.termux/files/usr/bin/python3.14" [ -f "$PAYLOAD_ABS/python/$PYBIN_REL" ] || fail "payload python tree lacks $PYBIN_REL" NODEBIN_REL="data/data/com.termux/files/usr/bin/node" [ -f "$PAYLOAD_ABS/node/$NODEBIN_REL" ] || fail "payload node tree lacks $NODEBIN_REL" @@ -130,7 +130,7 @@ docker run --rm --platform linux/arm64 \ # The staged tree is mounted at its REAL $PREFIX path so the venv # recorded absolute paths are correct on-device from birth. mkdir -p "$PREFIX" 2>/dev/null || true - PY="$PREFIX/lib/hermes-agent/tools/python$PREFIX/bin/python3.11" + PY="$PREFIX/lib/hermes-agent/tools/python$PREFIX/bin/python3.14" UV="$PREFIX/lib/hermes-agent/tools/uv$PREFIX/bin/uv" # Desktop payload canon: the venv holds the DEPENDENCY tree only; # the app runs from its own directory via PYTHONPATH (the wheel diff --git a/scripts/termux/build_wheels.py b/scripts/termux/build_wheels.py index 4f0d175679..86a1388077 100644 --- a/scripts/termux/build_wheels.py +++ b/scripts/termux/build_wheels.py @@ -218,15 +218,18 @@ def build_wheels(build_set: list[str], specs: dict[str, str], wheelhouse: Path) TARGET_ENV = { "implementation_name": "cpython", - "implementation_version": "3.11.15", + "implementation_version": "3.14.6", "os_name": "posix", "platform_machine": "aarch64", "platform_release": "", "platform_system": "Linux", "platform_version": "", - "python_full_version": "3.11.15", - "python_version": "3.11", - "sys_platform": "linux", + "python_full_version": "3.14.6", + "python_version": "3.14", + # 3.13+ Android CPython reports sys.platform "android" (docs: changed in + # 3.13), so markers keying on `sys_platform == 'linux'` no longer admit + # the termux target; `platform_system` stays "Linux" (Android kernel). + "sys_platform": "android", } # Mirrors the documented misses in termux_build.sh's probe (single diff --git a/scripts/termux/stage_runtime_libs.py b/scripts/termux/stage_runtime_libs.py index d20aa29b20..65b78f24ee 100644 --- a/scripts/termux/stage_runtime_libs.py +++ b/scripts/termux/stage_runtime_libs.py @@ -5,7 +5,7 @@ The sealed termux deb is self-contained by contract: the device's termux tree may have NONE of the payload interpreters' runtime libs installed (first real-device install: `import ctypes` dlopened libffi.so and died). The pin table (runtime_libs.json) is derived from the suppliers' own -dependency metadata -- the TUR python3.11 .deb's Depends line, uv's zstd, +dependency metadata -- the termux-main python .deb's Depends line, uv's zstd, nodejs's libc++/c-ares/libicu -- not from whichever lib happens to error first. diff --git a/scripts/termux/termux_build.sh b/scripts/termux/termux_build.sh index 8a14ef2567..58fa2a78fa 100755 --- a/scripts/termux/termux_build.sh +++ b/scripts/termux/termux_build.sh @@ -39,7 +39,7 @@ if [ "${1:-}" = "--in-container" ]; then # interpreter the phone will run. PAYLOAD_ROOT="${5:-}" export PREFIX=/data/data/com.termux/files/usr - STAGED_PY="$PAYLOAD_ROOT/python$PREFIX/bin/python3.11" + STAGED_PY="$PAYLOAD_ROOT/python$PREFIX/bin/python3.14" STAGED_UV="$PAYLOAD_ROOT/uv$PREFIX/bin/uv" # The staged binaries' RUNPATHs point at the phone's $PREFIX layout # (linkerconfig on-device). Inside the container the tree lives at @@ -88,7 +88,7 @@ if [ "${1:-}" = "--in-container" ]; then export UV_CONCURRENT_BUILDS=1 # Native extension links need the STAGED payload's libpython: the # container's own $PREFIX/lib (bootstrap only) is on the default - # -L path, but libpython3.11.so lives in the staged tree. setuptools + # -L path, but libpython3.14.so lives in the staged tree. setuptools # honors LDFLAGS, so every sdist build's link step finds it. STAGED_PYLIB="$PAYLOAD_ROOT/python$PREFIX/lib" export LDFLAGS="-L$STAGED_PYLIB ${LDFLAGS:-}" @@ -259,20 +259,22 @@ import json, re, sys, urllib.request from concurrent.futures import ThreadPoolExecutor # The TARGET environment the wheelhouse must satisfy: Termux's bionic -# python. TUR 3.11 reports sys.platform "linux" (the android value only -# arrived in 3.13), so markers keying on linux admit it -- and windows/ -# darwin markers exclude it, which is the whole point. +# python. termux-main 3.14 reports sys.platform "android" (the linux value +# applied to 3.11/3.12; 3.13 changed it), so markers keying on +# `sys_platform == 'linux'` no longer admit the termux target -- and +# `platform_system` stays "Linux" (Android kernel), admitting +# platform_system-gated deps. windows/darwin markers exclude it as before. TARGET_ENV = { "implementation_name": "cpython", - "implementation_version": "3.11.15", + "implementation_version": "3.14.6", "os_name": "posix", "platform_machine": "aarch64", "platform_release": "", "platform_system": "Linux", "platform_version": "", - "python_full_version": "3.11.15", - "python_version": "3.11", - "sys_platform": "linux", + "python_full_version": "3.14.6", + "python_version": "3.14", + "sys_platform": "android", } def locked_version(spec: str) -> str | None: @@ -358,7 +360,7 @@ fi # interpreter by construction. The payload must be staged before this. log "Building the wheelhouse inside the pinned container (payload ABI)" PAYLOAD_ABS="$OUT_ABS" -[ -f "$PAYLOAD_ABS/python/data/data/com.termux/files/usr/bin/python3.11" ] \ +[ -f "$PAYLOAD_ABS/python/data/data/com.termux/files/usr/bin/python3.14" ] \ || fail "staged payload python missing -- run build_cpython.sh first (the wheelhouse builds with the payload interpreter)" # The container mounts OUT_ABS at /out; translate the host-side work # paths before crossing the boundary (host absolutes do not exist inside). diff --git a/tests/hermes_cli/test_runtime_repair.py b/tests/hermes_cli/test_runtime_repair.py index 97b2555148..29e5b2183d 100644 --- a/tests/hermes_cli/test_runtime_repair.py +++ b/tests/hermes_cli/test_runtime_repair.py @@ -681,15 +681,15 @@ class TestMinorLineFallForward: def test_returns_none_with_bounded_attempts_when_all_minors_exhausted( self, tmp_path, monkeypatch ): - """When every build on every supported minor line (3.11-3.13) is + """When every build on every supported minor line (3.11-3.14) is vulnerable, the provisioner must give up with None -- and the total install workload must stay bounded by _MAX_PATCH_RETRIES per line.""" import hermes_cli.runtime_repair as runtime_repair install_calls = [] - resolutions = {"3.11": (3, 11, 14), "3.12": (3, 12, 30), "3.13": (3, 13, 30)} + resolutions = {"3.11": (3, 11, 14), "3.12": (3, 12, 30), "3.13": (3, 13, 30), "3.14": (3, 14, 30)} patch_lists = {} - for minor in (11, 12, 13): + for minor in (11, 12, 13, 14): versions = [(3, minor, v) for v in range(30, 10, -1)] # 20 patches patch_lists[f"3.{minor}"] = versions for version in versions: @@ -713,13 +713,14 @@ class TestMinorLineFallForward: cap = runtime_repair._MAX_PATCH_RETRIES # Per line: one bare request + at most _MAX_PATCH_RETRIES explicit - # patches; three lines total (3.11, 3.12, 3.13) and nothing beyond - # 3.13 (requires-python is <3.14). + # patches; four lines total (3.11, 3.12, 3.13, 3.14) and nothing beyond + # 3.14 (requires-python is <3.15). assert install_calls.count("3.11") == 1 assert install_calls.count("3.12") == 1 assert install_calls.count("3.13") == 1 - assert not any(call.startswith("3.14") for call in install_calls) - for minor in (11, 12, 13): + assert install_calls.count("3.14") == 1 + assert not any(call.startswith("3.15") for call in install_calls) + for minor in (11, 12, 13, 14): explicit = [ call for call in install_calls if call.startswith(f"3.{minor}.") @@ -727,7 +728,7 @@ class TestMinorLineFallForward: assert len(explicit) <= cap, ( f"3.{minor} explicit retries must be capped at {cap}: {explicit}" ) - assert len(install_calls) <= 3 * (1 + cap) + assert len(install_calls) <= 4 * (1 + cap) class TestListAvailablePatches: diff --git a/tests/pm/test_develop_env.py b/tests/pm/test_develop_env.py index bbeaa0ac2f..d4c98eab8e 100644 --- a/tests/pm/test_develop_env.py +++ b/tests/pm/test_develop_env.py @@ -53,7 +53,7 @@ def _fake_repo(tmp_path: Path, monkeypatch) -> Path: """A fake repo root with a synced venv, substituted for pm.paths.repo_root.""" repo = tmp_path / "repo" win = current_target().startswith("win32") - site = repo / "venv" / ("Lib" if win else "lib/python3.11") / "site-packages" + site = repo / "venv" / ("Lib" if win else "lib/python3.14") / "site-packages" site.mkdir(parents=True) monkeypatch.setattr(paths, "repo_root", lambda: repo) return repo @@ -63,7 +63,7 @@ def _develop_env(tmp_path, monkeypatch, *, with_python=True): _fake_store(tmp_path, monkeypatch, with_python=with_python) repo = _fake_repo(tmp_path, monkeypatch) win = current_target().startswith("win32") - site = repo / "venv" / ("Lib" if win else "lib/python3.11") / "site-packages" + site = repo / "venv" / ("Lib" if win else "lib/python3.14") / "site-packages" return pm_cli._develop_env(["faketool"]), repo, site diff --git a/tests/pm/test_pm_core.py b/tests/pm/test_pm_core.py index 41b40d781c..7a85b53789 100644 --- a/tests/pm/test_pm_core.py +++ b/tests/pm/test_pm_core.py @@ -490,12 +490,12 @@ def test_python_package_url_carries_release_tag(): from pm.registry import get_package python = get_package("python") - url = python.fetch_url("3.11.16+20260814", "win32-arm64") - assert "download/20260814/" in url - assert "cpython-3.11.16+20260814-aarch64-pc-windows-msvc-install_only" in url + url = python.fetch_url("3.14.7+20260901", "win32-arm64") + assert "download/20260901/" in url + assert "cpython-3.14.7+20260901-aarch64-pc-windows-msvc-install_only" in url try: - python.fetch_url("3.11.16", "win32-arm64") + python.fetch_url("3.14.7", "win32-arm64") raise AssertionError("bare version must be rejected") except PmInstallError: pass @@ -742,7 +742,7 @@ def test_python_stage_drops_unloadable_x64_vc_runtime_on_arm64(monkeypatch, tmp_ (staged / "vcruntime140.dll").write_bytes(b"arm64") monkeypatch.setattr(packages, "_macos_sign_managed_python", lambda p: False) - get_package("python").stage(None, staged, "3.11.16", "win32-arm64") + get_package("python").stage(None, staged, "3.14.7", "win32-arm64") assert not (staged / "vcruntime140_1.dll").exists() assert (staged / "vcruntime140.dll").is_file() @@ -757,7 +757,7 @@ def test_python_stage_keeps_vc_runtimes_on_other_targets(monkeypatch, tmp_path): (staged / "vcruntime140_1.dll").write_bytes(b"x64") monkeypatch.setattr(packages, "_macos_sign_managed_python", lambda p: False) - get_package("python").stage(None, staged, "3.11.16", "win32-x64") + get_package("python").stage(None, staged, "3.14.7", "win32-x64") assert (staged / "vcruntime140_1.dll").is_file() diff --git a/tests/pm/test_update.py b/tests/pm/test_update.py index 00b76e238f..d00787e915 100644 --- a/tests/pm/test_update.py +++ b/tests/pm/test_update.py @@ -37,7 +37,7 @@ def _pkg(pkg_name="node", version_style="semver", **latest): def test_version_key_sorts_numeric_and_suffixes(): assert version_key("2.53.0+5") > version_key("2.53.0+3") - assert version_key("3.11.16+20260814") > version_key("3.11.16+20260801") + assert version_key("3.14.7+20260901") > version_key("3.14.7+20260900") assert version_key("26.8.1") > version_key("26.7.0") assert version_key("10362") > version_key("10361") # prerelease-ish segments sort after numerics @@ -46,7 +46,7 @@ def test_version_key_sorts_numeric_and_suffixes(): def test_minor_of(): assert minor_of("26.7.0") == (26, 7) - assert minor_of("3.11.16+20260814") == (3, 11) + assert minor_of("3.14.7+20260901") == (3, 14) assert minor_of("10362") is None # single component diff --git a/website/docs/developer-guide/contributing.md b/website/docs/developer-guide/contributing.md index 4d3b00d576..5cd33fb56c 100644 --- a/website/docs/developer-guide/contributing.md +++ b/website/docs/developer-guide/contributing.md @@ -34,7 +34,7 @@ We value contributions in this order: | Requirement | Notes | | -------------------- | --------------------------------------------------------------------------------------------- | | **Git** | With the `git-lfs` extension installed | -| **Python 3.11–3.13** | uv will install it if missing | +| **Python 3.14** | uv will install it if missing | | **uv** | Fast Python package manager ([install](https://docs.astral.sh/uv/)) | | **Node.js 26+** | Optional — needed for browser tools and WhatsApp bridge (matches root `package.json` engines) | @@ -92,8 +92,8 @@ tree means no relative path from the workspace resolves to it. git clone https://github.com/NousResearch/hermes-agent.git cd hermes-agent -# Create venv with Python 3.11, OUTSIDE the source tree -uv venv ~/.hermes/venvs/hermes-dev --python 3.11 +# Create venv with Python 3.14, OUTSIDE the source tree +uv venv ~/.hermes/venvs/hermes-dev --python 3.14 export VIRTUAL_ENV="$HOME/.hermes/venvs/hermes-dev" export PATH="$VIRTUAL_ENV/bin:$PATH" diff --git a/website/docs/getting-started/installation.md b/website/docs/getting-started/installation.md index d28d8de0fc..51c077ff27 100644 --- a/website/docs/getting-started/installation.md +++ b/website/docs/getting-started/installation.md @@ -93,7 +93,7 @@ You don't need to rebuild your setup from scratch. Restore a full backup with `h **Installer:** On non-Windows platforms, the only prerequisite is **Git**. On Linux, also make sure `curl` and `xz-utils` are available (the installer downloads Node.js as a `.tar.xz` archive). The desktop app additionally requires `g++` (or `build-essential` on Debian/Ubuntu) to compile native modules. The installer automatically handles everything else: - **uv** (fast Python package manager) -- **Python 3.11** (via uv, no sudo needed) +- **Python 3.14** (via uv, no sudo needed) - **Node.js v26** (for browser automation and WhatsApp bridge; existing system Node 22.22+, 24.11+, or 26+ is used as-is) - **ripgrep** (fast file search) - **ffmpeg** (audio format conversion for TTS) diff --git a/website/docs/reference/faq.md b/website/docs/reference/faq.md index d9f04d7d27..6c35aa410a 100644 --- a/website/docs/reference/faq.md +++ b/website/docs/reference/faq.md @@ -146,7 +146,7 @@ The installer adds `~/.local/bin` to your PATH. If you use a non-standard shell #### Python version too old -**Cause:** Hermes requires Python 3.11 or newer. +**Cause:** Hermes requires Python 3.14 or newer. **Solution:** ```bash diff --git a/website/docs/user-guide/features/wake-word.md b/website/docs/user-guide/features/wake-word.md index 622532cd5f..d5bcab5097 100644 --- a/website/docs/user-guide/features/wake-word.md +++ b/website/docs/user-guide/features/wake-word.md @@ -126,8 +126,7 @@ wake_word: confirmation_frames: 3 # openWakeWord only — consecutive over-threshold frames required to fire start_new_session: true # start a fresh session on wake vs. continue the current one openwakeword: - model: hey_hermes # bundled default; OR a built-in name OR a path to a custom .onnx/.tflite - inference_framework: "" # "" (auto) | "onnx" | "tflite" + model: hey_hermes # bundled default; OR a built-in name OR a path to a custom .tflite porcupine: keyword: jarvis # built-in keyword OR path to a custom .ppn ``` @@ -165,13 +164,12 @@ The `sherpa` and `porcupine` engines decode the whole phrase internally, so they don't have the single-frame-spike problem and ignore `confirmation_frames` (but they still honor `sensitivity`). -`inference_framework` picks the openWakeWord backend. Leave it empty (the -default) to let Hermes choose per platform: **tflite on Apple Silicon**, onnx -everywhere else. openWakeWord's onnx backend returns near-zero scores on macOS -ARM64 ([openWakeWord#336](https://github.com/dscripka/openWakeWord/issues/336)), -so a listener pinned to `onnx` there will arm, show as listening, and never -fire. The tflite backend needs `ai-edge-litert` on macOS, which Hermes installs -on demand alongside the other wake-word deps. +The `openwakeword` engine is [pyopen-wakeword](https://github.com/rhasspy/pyopen-wakeword) +(rhasspy's maintained fork of openWakeWord): it runs TFLite via a library +bundled in its wheel and ships the same shared feature models openWakeWord +downloaded at runtime (byte-identical, verified by hash), so the shipped +`hey_hermes.tflite` scores exactly as before — with no runtime download and no +backend to pick. There is no `inference_framework` setting anymore. ### Surfaces (CLI, TUI, GUI) diff --git a/website/docs/user-guide/windows-native.md b/website/docs/user-guide/windows-native.md index 82ee8d6468..2d26106dc8 100644 --- a/website/docs/user-guide/windows-native.md +++ b/website/docs/user-guide/windows-native.md @@ -68,7 +68,7 @@ Each dep has a `shutil.which(...)`-style check; if a binary is missing and the r Top-to-bottom, in order: 1. **Bootstraps `uv`** — Astral's fast Python manager. Installed to `%USERPROFILE%\.local\bin`. -2. **Installs Python 3.11** via `uv`. No existing Python needed. +2. **Installs Python 3.14** via `uv`. No existing Python needed. 3. **Installs Node.js 26** (winget if available, else a portable Node tarball unpacked under `%LOCALAPPDATA%\hermes\node`). Used for the browser tool and the WhatsApp bridge. 4. **Installs portable Git** — if `git` is already on PATH the installer uses it; otherwise it downloads a trimmed, self-contained **PortableGit** (~45 MB, from the official `git-for-windows` release) to `%LOCALAPPDATA%\hermes\git`. No admin, no Windows installer registry, no interference with anything else on the box. 5. **Clones the repo** to `%LOCALAPPDATA%\hermes\hermes-agent` and creates a virtualenv inside it.