Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
188 commits
Select commit Hold shift + click to select a range
b7649ba
feat(desktop): add multilingual i18n support with 7 languages
Jun 4, 2026
01d79a2
feat(desktop): translate theme names, color modes, and provider descr…
Jun 4, 2026
dbb803d
fix(desktop): sync provider description keys with actual backend strings
Jun 4, 2026
bbf7d19
fix(desktop): use module-level providerDesc for API key descriptions
Jun 4, 2026
327318a
refactor(desktop): auto-discover locale files with import.meta.glob
Jun 4, 2026
7cd35e1
docs(desktop): add translation contribution guide for new languages
Jun 4, 2026
fe66a6c
feat(desktop): complete translations for Japanese, Korean, German, Sp…
Jun 4, 2026
9833d51
feat(desktop): add Traditional Chinese (zh-TW) and language switcher …
Jun 4, 2026
8bea25c
fix(desktop): 100% locale coverage, zh-TW→zh-Hant rename, globe→langu…
Jun 4, 2026
c12fa0c
fix(desktop): Globe+Language combined button, wider gap, instant loca…
Jun 4, 2026
b162d53
fix(desktop): replace Globe with WorldGlobe (latitude/longitude lines)
Jun 4, 2026
094dc0a
fix(desktop): inline SVG globe icon (standard latitude/longitude grid)
Jun 4, 2026
2e3aa2e
fix(desktop): remove focus ring on language button, fix statusbar loc…
Jun 4, 2026
cbfe68e
docs(desktop): add multi-language PR body and translation guide
Jun 4, 2026
5d62828
feat(i18n): add Portuguese (Brazil) locale - pt-BR.json
iaendi Jun 4, 2026
03b13ef
fix(i18n): add missing language.pt-BR key to 7 locales, fix normalize…
iaendi Jun 4, 2026
cdb4325
test(desktop): enforce locale catalog key parity (#1)
Yuxin-Qiao Jun 4, 2026
01ac7e1
merge: resolve upstream main conflicts
Jun 4, 2026
fd8aace
docs: update PR body with latest merge status
Jun 4, 2026
d9f55db
feat(desktop): compact language selector in appearance settings
Jun 4, 2026
f94a4e7
feat(i18n): add 6 new languages — Arabic, Hindi, Thai, Vietnamese, It…
Jun 4, 2026
e100c19
merge: resolve remaining conflicts, keep i18n changes
Jun 4, 2026
f5393a6
fix(i18n): apply starlit-dream zh-CN polish pass (49 corrections)
iaendi Jun 6, 2026
3c3e5a1
Merge remote-tracking branch 'upstream/main' into feat/i19n-desktop-m…
Jun 6, 2026
8bdfde9
Merge remote-tracking branch 'upstream/main' into feat/i18n-desktop-m…
Jun 7, 2026
0c229e9
Merge remote-tracking branch 'upstream/main' into feat/i18n-desktop-m…
Jun 8, 2026
c36dca6
auto: sync with upstream main
Jun 9, 2026
8cdd891
auto: sync with upstream main
Jun 9, 2026
30b82e1
auto: sync with upstream main
Jun 10, 2026
4d5de8e
auto: sync with upstream main
Jun 10, 2026
293cceb
auto: sync with upstream main
Jun 11, 2026
72ad179
test(i18n): cover all 15 languages in locale key parity test
iaendi Jun 11, 2026
ca194d1
fix(desktop): repair merge artifacts causing build failure
Jun 11, 2026
598320b
fix(desktop): add LocaleI18nProvider wrapper, fix test import conflict
Jun 11, 2026
2f1bfe1
auto: sync with upstream main
Jun 11, 2026
d237e73
auto: sync with upstream main
Jun 12, 2026
d6b406b
auto: sync conflicting files with upstream main (release.py, main.cjs…
Jun 12, 2026
6c015c6
auto: sync with upstream main
Jun 13, 2026
bec0ada
auto: sync with upstream main
Jun 13, 2026
e2930d2
auto: sync with upstream main
Jun 14, 2026
72ba825
auto: sync with upstream main
Jun 14, 2026
e980661
auto: sync with upstream main
Jun 15, 2026
04f4831
auto: sync with upstream main
Jun 15, 2026
b173d47
auto: sync with upstream main
Jun 16, 2026
048a6de
auto: sync with upstream main
Jun 16, 2026
14d2635
auto: sync with upstream main
Jun 17, 2026
b978eb5
auto: sync with upstream main
Jun 17, 2026
e5bccea
auto: sync with upstream main
Jun 18, 2026
df3aa24
auto: sync with upstream main
Jun 19, 2026
06ec465
auto: sync with upstream main
Jun 19, 2026
de2c55e
auto: sync with upstream main
Jun 20, 2026
b33c2b1
auto: sync with upstream main
Jun 20, 2026
1609752
auto: sync with upstream main
Jun 20, 2026
54c2015
auto: sync with upstream main
Jun 20, 2026
a38f774
auto: sync with upstream main
Jun 20, 2026
d329c97
auto: sync with upstream main
Jun 20, 2026
f53f99b
auto: sync with upstream main
Jun 21, 2026
b8a5bfa
auto: sync with upstream main
Jun 21, 2026
4fcf73a
auto: sync with upstream main
Jun 21, 2026
dce3ba2
auto: sync with upstream main
Jun 21, 2026
4921401
auto: sync with upstream main
Jun 21, 2026
c71c9b3
auto: sync with upstream main
Jun 22, 2026
aa2d644
auto: sync with upstream main
Jun 22, 2026
12e6509
auto: sync with upstream main
Jun 23, 2026
882a52b
auto: sync with upstream main
Jun 23, 2026
5896b08
auto: sync with upstream main
Jun 23, 2026
c3387a8
auto: sync with upstream main
Jun 23, 2026
52583fa
auto: sync with upstream main
Jun 23, 2026
90fb5a9
auto: sync with upstream main
Jun 24, 2026
e6134ef
auto: sync with upstream main
Jun 24, 2026
9c90476
auto: sync with upstream main
Jun 24, 2026
0918422
auto: sync with upstream main
Jun 24, 2026
780ac7e
auto: sync with upstream main
Jun 24, 2026
7aab6e4
auto: sync with upstream main
Jun 25, 2026
1337d12
auto: sync with upstream main
Jun 25, 2026
f455f18
auto: sync with upstream main
Jun 25, 2026
ef06897
auto: sync with upstream main
Jun 25, 2026
d981fe9
auto: sync with upstream main
Jun 25, 2026
5cdea7b
auto: sync with upstream main
Jun 26, 2026
ae4b98b
auto: sync with upstream main
Jun 26, 2026
2a25291
auto: sync with upstream main
Jun 26, 2026
cdd603a
auto: sync with upstream main
Jun 26, 2026
c8ea065
auto: sync with upstream main
Jun 26, 2026
519d50b
auto: sync with upstream main
Jun 26, 2026
3c5ef11
auto: sync with upstream main
Jun 27, 2026
c9b749e
auto: sync with upstream main
Jun 27, 2026
04252fb
auto: sync with upstream main
Jun 27, 2026
f564359
auto: sync with upstream main
Jun 27, 2026
f83f190
auto: sync with upstream main
Jun 27, 2026
1d247ad
auto: sync with upstream main
Jun 28, 2026
3e86f21
auto: sync with upstream main
Jun 28, 2026
901813b
auto: sync with upstream main
Jun 28, 2026
63d5351
auto: sync with upstream main
Jun 28, 2026
3f6af15
auto: sync with upstream main
Jun 28, 2026
e25fd7d
auto: sync with upstream main
Jun 29, 2026
ba0aace
auto: sync with upstream main
Jun 29, 2026
e644a50
auto: sync with upstream main
Jun 29, 2026
8562dc2
auto: sync with upstream main
Jun 29, 2026
4a72aad
auto: sync with upstream main
Jun 29, 2026
2b364d0
auto: sync with upstream main
Jun 30, 2026
e89dfe1
auto: sync with upstream main
Jun 30, 2026
f80d5e6
auto: sync with upstream main
Jun 30, 2026
8195de6
auto: sync with upstream main
Jun 30, 2026
1a0095e
auto: sync with upstream main
Jun 30, 2026
14057a7
auto: sync with upstream main
Jul 1, 2026
cc130ce
auto: sync with upstream main
Jul 1, 2026
faaf717
auto: sync with upstream main
Jul 1, 2026
0c2273d
auto: sync with upstream main
Jul 1, 2026
99faece
feat(desktop): add 11 new languages + sync-locales.py hybrid system
Jul 1, 2026
b03e109
fix(i18n): fix arrow function key names, types.ts regex corruption, a…
Jul 1, 2026
656f952
Merge branch 'i18n-hybrid' into feat/i18n-desktop-multilingual
Jul 1, 2026
55f34a4
auto: sync with upstream main
Jul 1, 2026
7d9fba6
auto: sync with upstream main [skip ci]
Jul 5, 2026
80bb161
auto: sync with upstream main [skip ci]
Jul 5, 2026
31ee69f
auto: sync with upstream main [skip ci]
Jul 5, 2026
8c39f8b
auto: sync with upstream main [skip ci]
Jul 5, 2026
92dd2d9
auto: sync with upstream main [skip ci]
Jul 6, 2026
24b8155
auto: sync with upstream main [skip ci]
Jul 6, 2026
55d5be5
auto: sync with upstream main [skip ci]
Jul 6, 2026
976b5f3
auto: sync with upstream main [skip ci]
Jul 6, 2026
78a0573
auto: sync with upstream main [skip ci]
Jul 6, 2026
574113b
auto: sync with upstream main [skip ci]
Jul 6, 2026
07f4c13
auto: sync with upstream main [skip ci]
Jul 6, 2026
a13066c
auto: sync with upstream main [skip ci]
Jul 7, 2026
f640e21
auto: sync with upstream main [skip ci]
Jul 7, 2026
cb8b18a
auto: sync with upstream main [skip ci]
Jul 7, 2026
ce5df98
auto: sync with upstream main [skip ci]
Jul 7, 2026
46d150c
auto: sync with upstream main [skip ci]
Jul 7, 2026
9335666
auto: sync with upstream main [skip ci]
Jul 7, 2026
b347d64
auto: sync with upstream main [skip ci]
Jul 7, 2026
42f71f8
auto: sync with upstream main [skip ci]
Jul 7, 2026
4faba4b
auto: sync with upstream main [skip ci]
Jul 7, 2026
9cf0152
auto: sync with upstream main [skip ci]
Jul 8, 2026
ed885ef
auto: sync with upstream main [skip ci]
Jul 8, 2026
eb0d125
auto: sync with upstream main [skip ci]
Jul 8, 2026
3bbb05c
auto: sync with upstream main [skip ci]
Jul 8, 2026
0142cc5
auto: sync with upstream main [skip ci]
Jul 8, 2026
26433ae
auto: sync with upstream main [skip ci]
Jul 8, 2026
942e7a8
auto: sync with upstream main [skip ci]
Jul 8, 2026
2ecdedf
auto: sync with upstream main [skip ci]
Jul 8, 2026
62459c1
auto: sync with upstream main [skip ci]
Jul 8, 2026
cb871a2
auto: sync with upstream main [skip ci]
Jul 8, 2026
c5458d9
auto: sync with upstream main [skip ci]
Jul 9, 2026
e33f7de
auto: sync with upstream main [skip ci]
Jul 9, 2026
8a48f51
auto: sync with upstream main [skip ci]
Jul 9, 2026
7e33c3a
auto: sync with upstream main [skip ci]
Jul 9, 2026
4664cb6
auto: sync with upstream main [skip ci]
Jul 9, 2026
2800da9
auto: sync with upstream main [skip ci]
Jul 9, 2026
556b2c3
auto: sync with upstream main [skip ci]
Jul 9, 2026
08eb544
auto: sync with upstream main [skip ci]
Jul 9, 2026
bd10f46
auto: sync with upstream main [skip ci]
Jul 10, 2026
2a8225a
auto: sync with upstream main [skip ci]
Jul 10, 2026
548a98c
auto: sync with upstream main [skip ci]
Jul 10, 2026
032aa77
auto: sync with upstream main [skip ci]
Jul 10, 2026
5453f4e
auto: sync with upstream main [skip ci]
Jul 10, 2026
0a1fe29
auto: sync with upstream main [skip ci]
Jul 10, 2026
f1c6dd1
auto: sync with upstream main [skip ci]
Jul 10, 2026
3299846
auto: sync with upstream main [skip ci]
Jul 10, 2026
c3fd35a
auto: sync with upstream main [skip ci]
Jul 10, 2026
bb8d45d
auto: sync with upstream main [skip ci]
Jul 10, 2026
776ca1b
auto: sync with upstream main [skip ci]
Jul 10, 2026
6cba9d1
auto: sync with upstream main [skip ci]
Jul 11, 2026
ba42c8d
auto: sync with upstream main [skip ci]
Jul 11, 2026
ac7a59d
auto: sync with upstream main [skip ci]
Jul 11, 2026
3eb6f16
auto: sync with upstream main [skip ci]
Jul 11, 2026
5c62964
auto: sync with upstream main [skip ci]
Jul 11, 2026
640473e
auto: sync with upstream main [skip ci]
Jul 11, 2026
8560274
auto: sync with upstream main [skip ci]
Jul 11, 2026
5738761
auto: sync with upstream main [skip ci]
Jul 11, 2026
1549d76
auto: sync with upstream main [skip ci]
Jul 11, 2026
89296d5
auto: sync with upstream main [skip ci]
Jul 12, 2026
459c92f
auto: sync with upstream main [skip ci]
Jul 12, 2026
c4a0a1b
auto: sync with upstream main [skip ci]
Jul 12, 2026
b6de6aa
auto: sync with upstream main [skip ci]
Jul 13, 2026
be4e626
auto: sync with upstream main [skip ci]
Jul 13, 2026
220d56b
auto: sync with upstream main [skip ci]
Jul 13, 2026
41e3931
auto: sync with upstream main [skip ci]
Jul 13, 2026
5daec1f
auto: sync with upstream main [skip ci]
Jul 13, 2026
4ac5326
auto: sync with upstream main [skip ci]
Jul 13, 2026
d3e8fae
auto: sync with upstream main [skip ci]
Jul 13, 2026
bff2bdf
auto: sync with upstream main [skip ci]
Jul 13, 2026
9296978
auto: sync with upstream main [skip ci]
Jul 13, 2026
42d6a25
auto: sync with upstream main [skip ci]
Jul 15, 2026
9024a6b
fix(i18n): sync upstream main, fix sweeper issues, resolve type error…
Jul 15, 2026
a57d378
fix(i18n): polish Russian desktop JSON locale
DrMaks22 Jul 15, 2026
1d343bb
auto: sync with upstream main [skip ci]
Jul 23, 2026
76d34c7
fix(i18n): resolve 561 TS type errors — sync type annotations, JSON k…
Jul 23, 2026
a797528
auto: sync with upstream main [skip ci]
Jul 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
13 changes: 0 additions & 13 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,6 @@
# HF_BASE_URL=https://router.huggingface.co/v1 # Override default base URL
# OPENCODE_GO_BASE_URL=https://opencode.ai/zen/go/v1 # Override default base URL

# DeepInfra — 100+ top open models, pay-per-use.
# Get your key at: https://deepinfra.com/dash/api_keys
# DEEPINFRA_API_KEY=

# =============================================================================
# LLM PROVIDER (Qwen OAuth)
# =============================================================================
Expand All @@ -136,15 +132,6 @@
# Optional base URL override:
# XIAOMI_BASE_URL=https://api.xiaomimimo.com/v1

# =============================================================================
# LLM PROVIDER (Upstage Solar)
# =============================================================================
# Upstage provides access to Upstage Solar models.
# Get your key at: https://console.upstage.ai/api-keys
# UPSTAGE_API_KEY=your_key_here
# Optional base URL override:
# UPSTAGE_BASE_URL=https://api.upstage.ai/v1

# =============================================================================
# TOOL API KEYS
# =============================================================================
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/detect-changes/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ outputs:
description: Run Python tests / ruff / ty / windows-footguns.
value: ${{ steps.classify.outputs.python }}
frontend:
description: Run the TypeScript testing matrix + desktop build.
description: Run the TypeScript typecheck matrix + desktop build.
value: ${{ steps.classify.outputs.frontend }}
docker_meta:
description: Docker setup and meta files have changed.
Expand Down
34 changes: 9 additions & 25 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,11 @@ jobs:
with:
event_name: ${{ needs.detect.outputs.event_name }}

js-tests:
name: JS & TS checks
typecheck:
name: TypeScript
needs: detect
if: needs.detect.outputs.frontend == 'true'
uses: ./.github/workflows/js-tests.yml

e2e-desktop:
name: Desktop E2E
needs: detect
if: needs.detect.outputs.python == 'true' || needs.detect.outputs.frontend == 'true'
uses: ./.github/workflows/e2e-desktop.yml
uses: ./.github/workflows/typecheck.yml

docs-site:
name: Docs Site
Expand Down Expand Up @@ -251,8 +245,7 @@ jobs:
- detect
- tests
- lint
- js-tests
- e2e-desktop
- typecheck
- docs-site
- history-check
- contributor-check
Expand All @@ -274,23 +267,14 @@ jobs:
- name: Evaluate job results
id: evaluate
env:
NEEDS: ${{ toJSON(needs) }}
RESULTS: ${{ toJSON(needs.*.result) }}
run: |
echo "$NEEDS" | python3 -c "
echo "$RESULTS" | python3 -c "
import json, sys
needs = json.load(sys.stdin)
# Emit compact {job_name: result} for the comment assembler.
compact = {name: info['result'] for name, info in needs.items()}
print(f'needs-json={json.dumps(compact)}')
with open('$GITHUB_OUTPUT', 'a') as f:
f.write(f'needs-json={json.dumps(compact)}\n')
failed = [name for name, info in needs.items() if info['result'] == 'failure']
for name, info in sorted(needs.items()):
result = info['result']
icon = '✅' if result in ('success', 'skipped') else '❌'
print(f'{icon} {name}: {result}')
results = json.load(sys.stdin)
failed = [r for r in results if r == 'failure']
if failed:
print(f'::error::{len(failed)} job(s) failed: {\", \".join(failed)}')
print(f'::error::{len(failed)} job(s) failed')
sys.exit(1)
print('All checks passed (or were skipped)')
"
Expand Down
30 changes: 9 additions & 21 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -207,41 +207,29 @@ jobs:

- name: Create manifest list and push
working-directory: /tmp/digests
env:
IMAGE_NAME: ${{ env.IMAGE_NAME }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
set -euo pipefail
args=()
for digest_file in *; do
args+=("${IMAGE_NAME}@sha256:${digest_file}")
done
if [ "${{ github.event_name }}" = "release" ]; then
tags=(-t "${IMAGE_NAME}:${RELEASE_TAG}")
TAG="${{ github.event.release.tag_name }}"
docker buildx imagetools create \
-t "${IMAGE_NAME}:${TAG}" \
"${args[@]}"
else
tags=(-t "${IMAGE_NAME}:main" -t "${IMAGE_NAME}:latest")
fi
# Retry: Docker Hub API + just-pushed digest eventual consistency
# can transiently fail the create; the operation is idempotent.
for i in 1 2 3; do
if docker buildx imagetools create "${tags[@]}" "${args[@]}"; then
break
fi
if [ "$i" = 3 ]; then
echo "::error::imagetools create failed after 3 attempts"
exit 1
fi
echo "::warning::imagetools create failed (attempt $i); retrying in 20s"
sleep 20
done

- name: Inspect image
env:
IMAGE_NAME: ${{ env.IMAGE_NAME }}
RELEASE_TAG: ${{ github.event.release.tag_name }}

- name: Inspect image
run: |
if [ "${{ github.event_name }}" = "release" ]; then
docker buildx imagetools inspect "${IMAGE_NAME}:${RELEASE_TAG}"
docker buildx imagetools inspect "${IMAGE_NAME}:${{ github.event.release.tag_name }}"
else
docker buildx imagetools inspect "${IMAGE_NAME}:main"
fi
env:
IMAGE_NAME: ${{ env.IMAGE_NAME }}
19 changes: 13 additions & 6 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
name: Tests

on:
workflow_call:
inputs:
slice_count:
description: Number of parallel test slices
type: number
default: 8
push:
branches: [main]
paths-ignore:
- "**/*.md"
- "docs/**"
# No paths filter — the job must always run so the required check
# reports a status (path-gated workflows leave checks "pending" forever
# when no matching files change, which blocks merge).
pull_request:
branches: [main]
paths-ignore:
- "**/*.md"
- "docs/**"

permissions:
contents: read
Expand Down
51 changes: 51 additions & 0 deletions .github/workflows/typecheck.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# .github/workflows/typecheck.yml
name: Typecheck

on:
workflow_call:

jobs:
typecheck:
name: Check TypeScript
runs-on: ubuntu-latest
strategy:
matrix:
package:
[ui-tui, web, apps/bootstrap-installer, apps/desktop, apps/shared]
fail-fast: false # report all failures, not just the first one
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22
cache: npm
# --ignore-scripts: typecheck only needs the TS sources + type defs, not
# native builds. Skipping install scripts drops node-pty's node-gyp
# header fetch — the transient flake that killed this job pre-`tsc` — and
# is faster. retry covers the remaining registry blips.
- uses: ./.github/actions/retry
with:
command: npm ci --ignore-scripts
- run: npm run --prefix ${{ matrix.package }} typecheck

# Production build of the desktop renderer. `typecheck` runs `tsc` only,
# which does NOT exercise Vite/Rolldown module resolution — so an
# unresolvable package export (e.g. a transitive @assistant-ui/tap that no
# longer exports "./react-shim") slips past typecheck and only explodes when
# users build apps/desktop from source on install/update. Run the real
# `vite build` here so that class of break fails in CI instead.
desktop-build:
name: Build desktop app
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22
cache: npm
# Keep install scripts here: the production build may need node-pty's
# native binary. retry handles the transient install-time fetch flakes.
- uses: ./.github/actions/retry
with:
command: npm ci
- run: npm run --prefix apps/desktop build
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ apps/shared/src/**/*.js
apps/shared/src/**/*.js.map
apps/shared/src/**/*.d.ts
apps/desktop/release/
*.tsbuildinfo
apps/desktop/*.tsbuildinfo

# Web UI assets — synced from @nous-research/ui at build time via
# `npm run sync-assets` (see web/package.json).
Expand Down
57 changes: 1 addition & 56 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ The dashboard embeds the real `hermes --tui` — **not** a rewrite. See `hermes

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

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.ts` + `backendSupportsServe()` in `electron/main.ts`) 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. For scoped Desktop architecture, state, resolver, transport, and testing rules, read `apps/desktop/AGENTS.md`.
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`.

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

Expand Down Expand Up @@ -1377,58 +1377,3 @@ not the specific names.

Reviewers should reject new change-detector tests; authors should convert
them into invariants before re-requesting review.

### Never read source code in tests

A test that reads a source file's text is testing *the shape of the
source code*, not its behavior. This is a hard antipattern, banned outright.
Any test that reads a .py, .ts, .tsx, etc., file is suspect.

**Why it's actively harmful, not just weak:**

- It passes when the implementation is subtly broken (the regex matches a
call site that exists but is wired wrong) and fails when a correct
refactor changes formatting, variable names, or control flow with
identical runtime behavior. Both directions of failure are wrong.
- It can't be run against a built/bundled/minified artifact, so it silently
stops testing anything the moment code moves, gets renamed, or a
dependency reformats it.
- It actively blocks refactors: reviewers see "keeps a pattern intact" tests
fail during pure structural cleanup with no behavior change, and either
hand-wave the failure (dangerous) or waste time updating regexes that add
nothing (waste).
- It gives false confidence. a green suite full of source-regex tests
looks like coverage but has never once executed the code path it claims
to guard.

**Do not write:**

```ts
const source = fs.readFileSync(path.join(__dirname, 'main.ts'), 'utf8')

test('backend spawn hides the Windows console', () => {
assert.match(source, /spawn\(\s*backend\.command,\s*backend\.args[\s\S]{0,300}hiddenWindowsChildOptions/)
})
```

**Do write — extract the logic into a small pure/DI-testable function and
call it for real:**

```ts
// backend-spawn.ts
export function hiddenWindowsChildOptions(options: SpawnOptionsLike = {}, isWindows = process.platform === 'win32') {
if (!isWindows || 'windowsHide' in options) return options
return { ...options, windowsHide: true }
}

// backend-spawn.test.ts
test('windowsHide defaults to true on Windows, is left alone elsewhere', () => {
assert.equal(hiddenWindowsChildOptions({}, true).windowsHide, true)
assert.equal(hiddenWindowsChildOptions({}, false).windowsHide, undefined)
assert.equal(hiddenWindowsChildOptions({ windowsHide: false }, true).windowsHide, false)
})
```

If the logic lives inline in a god-file (`main.ts`, `cli.py`,
`gateway/run.py`) and extracting it feels disruptive: that's the actual
signal to do the extraction, not to regex around it.
35 changes: 1 addition & 34 deletions acp_adapter/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from __future__ import annotations

import json
import logging
import uuid
from typing import Any, Dict, List, Optional

Expand All @@ -15,8 +14,6 @@
ToolKind,
)

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Map hermes tool names -> ACP ToolKind
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -1047,37 +1044,7 @@ def build_tool_start(
*,
edit_diff: Any = None,
) -> ToolCallStart:
"""Create a ToolCallStart event for the given hermes tool invocation.

A malformed tool argument (e.g. a non-string ``command``/``path`` from a
model that ignores the schema) must never abort the ACP tool-call render —
``build_tool_start`` runs on the live tool-progress callback and during
session history replay. On any failure in the title/content/location
builders, fall back to a minimal, valid start event. Mirrors
``get_cute_tool_message`` in ``agent/display.py``, wrapped for the same
reason on the CLI side.
"""
try:
return _build_tool_start(
tool_call_id, tool_name, arguments, edit_diff=edit_diff
)
except Exception as exc: # noqa: BLE001 — a tool-call render must never abort the turn
logger.debug("ACP tool-start render failed for %r: %s", tool_name, exc)
safe_name = tool_name if isinstance(tool_name, str) and tool_name else "tool"
return acp.start_tool_call(
tool_call_id, safe_name, kind=get_tool_kind(safe_name),
content=None, locations=[], raw_input=None,
)


def _build_tool_start(
tool_call_id: str,
tool_name: str,
arguments: Dict[str, Any],
*,
edit_diff: Any = None,
) -> ToolCallStart:
"""Build the ToolCallStart event (unguarded; see ``build_tool_start``)."""
"""Create a ToolCallStart event for the given hermes tool invocation."""
kind = get_tool_kind(tool_name)
title = build_tool_title(tool_name, arguments)
locations = extract_locations(arguments)
Expand Down
Loading