Skip to content

Commit 94e3b26

Browse files
committed
Merge remote-tracking branch 'upstream/main' into test/upstream-catchup-20260710
2 parents 49fc5dd + 5e84994 commit 94e3b26

1,260 files changed

Lines changed: 114104 additions & 14176 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.envrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
watch_file pyproject.toml uv.lock
1+
watch_file pyproject.toml uv.lock hermes
22
watch_file package-lock.json package.json web/package.json ui-tui/package.json website/package.json apps/shared/package.json apps/desktop/package.json ui-tui/packages/hermes-ink/package.json
33
watch_file flake.nix flake.lock nix/devShell.nix nix/tui.nix nix/package.nix nix/python.nix nix/hermes-agent.nix nix/desktop.nix
44

.github/workflows/ci.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,10 +221,17 @@ jobs:
221221
222222
- name: Save baseline cache (main only)
223223
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
224-
run: cp ci-timings.json ci-timings-baseline.json
224+
run: |
225+
# Degraded runs (API rate-limited) produce no ci-timings.json —
226+
# skip rather than fail, and never cache an empty baseline.
227+
if [ -f ci-timings.json ]; then
228+
cp ci-timings.json ci-timings-baseline.json
229+
else
230+
echo "No timings JSON this run — skipping baseline update"
231+
fi
225232
226233
- name: Upload baseline to cache (main only)
227-
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
234+
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && hashFiles('ci-timings-baseline.json') != ''
228235
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
229236
with:
230237
path: ci-timings-baseline.json

.github/workflows/docker.yml

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -178,32 +178,33 @@ jobs:
178178

179179
- name: Create manifest list and push
180180
working-directory: /tmp/digests
181+
env:
182+
IMAGE_NAME: ${{ env.IMAGE_NAME }}
183+
RELEASE_TAG: ${{ github.event.release.tag_name }}
181184
run: |
182185
set -euo pipefail
183186
args=()
184187
for digest_file in *; do
185188
args+=("${IMAGE_NAME}@sha256:${digest_file}")
186189
done
187190
if [ "${{ github.event_name }}" = "release" ]; then
188-
TAG="${{ github.event.release.tag_name }}"
189191
docker buildx imagetools create \
190-
-t "${IMAGE_NAME}:${TAG}" \
192+
-t "${IMAGE_NAME}:${RELEASE_TAG}" \
191193
"${args[@]}"
192194
else
193195
docker buildx imagetools create \
194196
-t "${IMAGE_NAME}:main" \
195197
-t "${IMAGE_NAME}:latest" \
196198
"${args[@]}"
197199
fi
198-
env:
199-
IMAGE_NAME: ${{ env.IMAGE_NAME }}
200200
201201
- name: Inspect image
202+
env:
203+
IMAGE_NAME: ${{ env.IMAGE_NAME }}
204+
RELEASE_TAG: ${{ github.event.release.tag_name }}
202205
run: |
203206
if [ "${{ github.event_name }}" = "release" ]; then
204-
docker buildx imagetools inspect "${IMAGE_NAME}:${{ github.event.release.tag_name }}"
207+
docker buildx imagetools inspect "${IMAGE_NAME}:${RELEASE_TAG}"
205208
else
206209
docker buildx imagetools inspect "${IMAGE_NAME}:main"
207210
fi
208-
env:
209-
IMAGE_NAME: ${{ env.IMAGE_NAME }}

.github/workflows/lint.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,16 @@ jobs:
9898
echo "base ty: $(wc -c < .lint-reports/base/ty.json) bytes"
9999
100100
- name: Generate diff summary
101+
env:
102+
HEAD_REF: ${{ inputs.event_name == 'pull_request' && github.head_ref || github.ref_name }}
101103
run: |
102104
python scripts/lint_diff.py \
103105
--base-ruff .lint-reports/base/ruff.json \
104106
--head-ruff .lint-reports/head/ruff.json \
105107
--base-ty .lint-reports/base/ty.json \
106108
--head-ty .lint-reports/head/ty.json \
107109
--base-ref "${{ steps.base.outputs.ref }}" \
108-
--head-ref "${{ inputs.event_name == 'pull_request' && github.head_ref || github.ref_name }}" \
110+
--head-ref "$HEAD_REF" \
109111
--output .lint-reports/summary.md
110112
cat .lint-reports/summary.md >> "$GITHUB_STEP_SUMMARY"
111113

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ __pycache__/
88
.venv
99
.vscode/
1010
.env
11+
.op.env
1112
.env.local
1213
.env.development.local
1314
.env.test.local
@@ -118,6 +119,9 @@ docs/superpowers/*
118119
# treat it as a local edit and autostash it on every run (#38529).
119120
.hermes-bootstrap-complete
120121

122+
# Persistent dev sandbox dir (scripts/dev-sandbox.sh --persistent)
123+
.hermes-sandbox/
124+
121125
# Interrupted-update breadcrumb + recovery lock written next to the shared venv
122126
# by `hermes update` / launch-time self-heal. Runtime state, never a code change
123127
# — ignore so `git status` stays clean and update's autostash skips them.

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ The dashboard embeds the real `hermes --tui` — **not** a rewrite. See `hermes
491491

492492
### Electron Desktop Chat App (`apps/desktop/`)
493493

494-
A **separate** chat surface from both the classic CLI and the dashboard's embedded TUI. It is an Electron + React + nanostore renderer (`@assistant-ui/react`) that talks to a `tui_gateway` backend over JSON-RPC (`requestGateway(method, params)`). The WebSocket/JSON-RPC transport lives in the framework-agnostic `apps/shared` package (`@hermes/shared` — `JsonRpcGatewayClient` + WS URL helpers), which the web dashboard (`web/`) also consumes; **desktop has no build/runtime dependency on the dashboard frontend** — it spawns a headless `hermes serve` backend server (the same gateway `dashboard` serves, minus the browser UI). `dashboard` and `serve` share `cmd_dashboard`/`start_server` but are independent surfaces — neither launches the other. The one exception is a backward-compat *fallback*: `serve` is newer, so the desktop spawn (`electron/backend-command.cjs` + `backendSupportsServe()` in `main.cjs`) detects whether the resolved runtime registers `serve` and, only when it does not (an older managed install / PATH `hermes` the app hasn't updated yet), rewrites the argv to the legacy `dashboard --no-open`. Without that, a new app against an un-upgraded runtime would crash on an unknown subcommand and brick every mid-upgrade user. It does NOT embed `hermes --tui` — it has its own composer, transcript, and slash-command pipeline. Route desktop bugs to the `hermes-desktop-app-work` skill, not `hermes-dashboard-work`.
494+
A **separate** chat surface from both the classic CLI and the dashboard's embedded TUI. It is an Electron + React + nanostore renderer (`@assistant-ui/react`) that talks to a `tui_gateway` backend over JSON-RPC (`requestGateway(method, params)`). The WebSocket/JSON-RPC transport lives in the framework-agnostic `apps/shared` package (`@hermes/shared` — `JsonRpcGatewayClient` + WS URL helpers), which the web dashboard (`web/`) also consumes; **desktop has no build/runtime dependency on the dashboard frontend** — it spawns a headless `hermes serve` backend server (the same gateway `dashboard` serves, minus the browser UI entirely: `serve` sets `headless_backend=True`, so `cmd_dashboard` skips `_build_web_ui` AND exports `HERMES_SERVE_HEADLESS=1` so `mount_spa()` disables the SPA even if a stray `web_dist/` exists — only the JSON-RPC/WS/API surface is reachable). `dashboard` and `serve` share `cmd_dashboard`/`start_server` but are independent surfaces — neither launches the other. The one exception is a backward-compat *fallback*: `serve` is newer, so the desktop spawn (`electron/backend-command.cjs` + `backendSupportsServe()` in `main.cjs`) detects whether the resolved runtime registers `serve` and, only when it does not (an older managed install / PATH `hermes` the app hasn't updated yet), rewrites the argv to the legacy `dashboard --no-open`. Without that, a new app against an un-upgraded runtime would crash on an unknown subcommand and brick every mid-upgrade user. It does NOT embed `hermes --tui` — it has its own composer, transcript, and slash-command pipeline. Route desktop bugs to the `hermes-desktop-app-work` skill, not `hermes-dashboard-work`.
495495

496496
**Slash commands in the desktop app are curated client-side, then dispatched to the backend.** The pipeline:
497497

CONTRIBUTING.es.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ Esto no es una barra de calidad — es una decisión de acoplamiento y mantenimi
7474
| Requisito | Notas |
7575
|-----------|-------|
7676
| **Git** | Con la extensión `git-lfs` instalada |
77-
| **Python 3.11+** | uv lo instalará si falta |
77+
| **Python 3.11–3.13** | uv lo instalará si falta |
7878
| **uv** | Gestor de paquetes Python rápido ([instalar](https://docs.astral.sh/uv/)) |
7979
| **Node.js 20+** | Opcional — necesario para herramientas de navegador y puente WhatsApp (coincide con los engines de `package.json` raíz) |
8080

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ A well-built third-party-product plugin can clear automated review and still be
109109
| Requirement | Notes |
110110
|-------------|-------|
111111
| **Git** | With the `git-lfs` extension installed |
112-
| **Python 3.11+** | uv will install it if missing |
112+
| **Python 3.11–3.13** | uv will install it if missing |
113113
| **uv** | Fast Python package manager ([install](https://docs.astral.sh/uv/)) |
114114
| **Node.js 20+** | Optional — needed for browser tools and WhatsApp bridge (matches root `package.json` engines) |
115115

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,7 @@ graft locales
77
# built from the sdist (e.g. Homebrew, downstream packagers). package-data
88
# below covers the wheel; this covers the sdist. See #34034 / #28149.
99
recursive-include plugins plugin.yaml plugin.yml
10+
# Gateway assets include images plus YAML catalogs such as status_phrases.yaml.
11+
recursive-include gateway/assets *
1012
global-exclude __pycache__
1113
global-exclude *.py[cod]

acp_adapter/tools.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,12 @@ def build_tool_title(tool_name: str, args: Dict[str, Any]) -> str:
110110
if tool_name == "web_extract":
111111
urls = args.get("urls", [])
112112
if urls:
113-
return f"extract: {urls[0]}" + (f" (+{len(urls)-1})" if len(urls) > 1 else "")
113+
first = urls[0]
114+
if isinstance(first, dict):
115+
first = first.get("url") or first.get("href") or "?"
116+
elif not isinstance(first, str):
117+
first = "?"
118+
return f"extract: {first}" + (f" (+{len(urls)-1})" if len(urls) > 1 else "")
114119
return "web extract"
115120
if tool_name == "process":
116121
action = str(args.get("action") or "").strip() or "manage"
@@ -617,7 +622,7 @@ def _format_session_search_result(result: Optional[str]) -> Optional[str]:
617622
return None
618623
mode = data.get("mode") or "search"
619624
query = data.get("query")
620-
lines = ["Recent sessions" if mode == "recent" else f"Session search results" + (f" for `{query}`" if query else "")]
625+
lines = ["Recent sessions" if mode == "recent" else "Session search results" + (f" for `{query}`" if query else "")]
621626
if not results:
622627
lines.append(str(data.get("message") or "No matching sessions found."))
623628
return "\n".join(lines)

0 commit comments

Comments
 (0)