Commit Graph

1351 Commits (812236bff852a74a9fbdec4725ac58faae94de56)

Author SHA1 Message Date
teknium 838daca9f4 chore(desktop): format tooltip indentation + author map for #49697
Re-indent the salvaged title= lines to spaces (prettier), and map
alelpoan@proton.me in the release author map.
2026-06-20 10:45:14 -07:00
kshitijk4poor e49272fe53 chore(release): map w31rdm4ch1nZ contributor email to GitHub login 2026-06-20 20:51:41 +05:30
kshitijk4poor ef7e716930 chore(release): map rratmansky contributor email to GitHub login 2026-06-20 16:24:15 +05:30
kshitijk4poor 467c879b2e chore(release): map lkz-de contributor email to GitHub login
The contributor-check CI auto-resolves only the +id form of GitHub noreply
emails; lkz-de's commits use the legacy plain form
(lkz-de@users.noreply.github.com), so add an explicit AUTHOR_MAP entry.
2026-06-20 15:03:29 +05:30
teknium1 6504f51cd5 chore: add @hakanpak to AUTHOR_MAP for PR #49282 salvage 2026-06-19 16:59:54 -07:00
joaomarcos 5649b8649a Fix silent delivery failures in Signal live adapter (#49260) 2026-06-19 16:59:38 -07:00
teknium1 2bd1977d8f chore: release v0.17.0 (2026.6.19) 2026-06-19 12:38:31 -07:00
teknium1 746c46d610 chore: add lgalabru to AUTHOR_MAP for PR #43112 salvage 2026-06-19 11:46:25 -07:00
teknium1 22ccb12c30 chore(release): map skyzh@mail.build to xxchan for Raft salvage
CI blocks PRs with unmapped commit-author emails.
2026-06-19 07:52:37 -07:00
Teknium 1958208744 chore(release): add Sahil-SS9 to AUTHOR_MAP for PRs #48466/#44919/#44909/#42209 2026-06-19 07:35:29 -07:00
teknium1 4128c69799 chore: add carlos.dddo to AUTHOR_MAP 2026-06-19 07:16:57 -07:00
teknium1 1cc915763b test(cli): cover cli_refresh_interval default; map salvaged author
Follow-up to the salvaged #48312 — adds the config-default test (ported
from #48319) and the AUTHOR_MAP entry for the cherry-picked commit.
2026-06-19 07:06:34 -07:00
teknium1 ddca590cac chore: add Cdddo to AUTHOR_MAP 2026-06-19 07:04:58 -07:00
teknium b922d7dfb2 chore(release): add salesondemandio to AUTHOR_MAP for PR #42664 2026-06-19 06:31:56 -07:00
teknium1 2c3aebcadc fix(clarify): unwrap dict choices at the source so every surface gets clean text
The Discord fix (previous commit) handles dict-shaped clarify choices at the
Discord adapter only. The same dict-repr leak originates upstream at
tools/clarify_tool.py's str(c).strip() normalization — the single
platform-agnostic point both the CLI and every gateway adapter flow through.

When an LLM emits [{"description": "..."}] instead of bare strings, str(c)
produced {'description': '...'} which leaked onto the CLI panel
(cli.py:13048/13081), was returned verbatim as the user's answer
(cli.py:11945), and hit Telegram's numbered list too.

Add _flatten_choice (same label->description->text->title unwrap as the
Discord adapter, name/value excluded, keyless dicts dropped) and apply it at
the normalization line. Fixes CLI + Telegram + all platforms at the root;
the Discord smart-truncation now operates on already-clean text.

Adds johnjacobkenny to AUTHOR_MAP for the salvaged commit.
2026-06-19 06:31:08 -07:00
kshitijk4poor fd27c90870 chore: add tt-a1i to AUTHOR_MAP
For PR #48933 (SSE-only Anthropic stream aggregation, fixes #48923).
2026-06-19 15:46:14 +05:30
Hao Zhe 5a856bdfa3 chore(release): add OpenViking contributor attribution 2026-06-19 15:38:25 +08:00
kshitij d06104a9ee
fix(dashboard): resolve chat TUI argv off event loop (#48561)
* fix(dashboard): resolve chat TUI argv off event loop

Dashboard chat now resolves its TUI launch command off the
FastAPI/WebSocket event loop. The resolver can run `npm install` /
`npm run build` through `_make_tui_argv()`, and doing that synchronously
in `/api/pty` can block proxy keepalives and other dashboard WebSocket
work long enough for reverse-proxy deployments to drop the chat
connection.

This keeps the current TUI build policy intact: normal production
launches still run the correctness-first `npm run build` path, while
`HERMES_TUI_DIR` remains the prebuilt/no-build path for distros and
containers. The change only moves the potentially slow resolver work to
a worker thread for the dashboard chat path, serialized by an
`asyncio.Lock` so concurrent chat tabs preserve one-build-at-a-time
behavior. `SystemExit` (node/npm missing) and the profile `HTTPException`
path still propagate cleanly through `asyncio.to_thread()`.

Salvaged from #26124 — rebased onto current main. The async wrapper now
threads the `profile` parameter that `_resolve_chat_argv` gained on main
since the PR was opened, so cross-profile chat is preserved.

Co-authored-by: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com>

* chore: add 0xdany to AUTHOR_MAP

* fix(dashboard): bind chat-argv lock to app.state; cover error propagation

Self-review hardening on top of the salvaged fix:

- Move `_chat_argv_lock` from a module-level `asyncio.Lock()` onto
  `app.state` (initialised in `_lifespan`, lazy fallback via
  `_get_chat_argv_lock`), mirroring `event_lock`. A module-level
  `asyncio.Lock()` binds to whatever event loop is active at import time,
  which is the exact pattern `_get_event_state`'s docstring warns against
  (breaks across TestClient instances / uvicorn reloads). This keeps the
  lock on the running loop.
- Add two tests exercising the real `_resolve_chat_argv_async` →
  `asyncio.to_thread` → lock → re-raise chain: `SystemExit` (node/npm
  missing) and `HTTPException` (invalid profile) both propagate out of the
  worker thread and are caught by `pty_ws`'s existing handlers. The prior
  tests mocked `asyncio.to_thread` away and never covered this path.

* test(dashboard): dedupe pty error-propagation tests; assert close code

simplify-code cleanup pass on the salvage stack:

- Extract the shared scaffolding of the two pty_ws error-propagation tests
  into `_assert_pty_propagates`, keeping the two tests as distinct contracts
  for the `except SystemExit` and `except HTTPException` arms.
- Assert the stable WebSocket close code (1011) instead of relying solely on
  the user-facing "Chat unavailable" notice wording — a behavior contract per
  the AGENTS.md "behavior contracts over snapshots" rule, robust to notice
  rewording. The detail substring ("unknown profile") is still checked for the
  HTTPException case since proving the detail survives the thread hop is the
  point of that test.

No production-code change; the helper exercises the same real
_resolve_chat_argv_async -> asyncio.to_thread -> lock -> re-raise chain.

---------

Co-authored-by: draihan <draihan@student.ubc.ca>
2026-06-18 22:20:52 -04:00
teknium 8568988b01 chore: add JoaoMarcos44 to AUTHOR_MAP 2026-06-18 19:15:04 -07:00
teknium1 62c71ebd8f chore(release): map chanyoung.kim@nota.ai -> channkim for #47049 salvage 2026-06-18 19:14:52 -07:00
Victor Kyriazakos 3ead2bdd0d feat(prompt): configurable per-platform system-prompt hint overrides
Add platform_hints config so an admin can append to or replace Hermes'
built-in platform hint for a single messaging platform (WhatsApp, Slack,
Telegram, ...) without affecting other platforms. Enables enterprise
managed profiles to steer platform-aware skills (e.g. invoke a custom
table-formatting skill on WhatsApp where Markdown tables don't render)
while leaving Telegram/Slack/CLI behavior unchanged.

- hermes_cli/config.py: document platform_hints in DEFAULT_CONFIG
- agent/agent_init.py: load platform_hints -> agent._platform_hint_overrides
- agent/system_prompt.py: _resolve_platform_hint() applies append/replace
  (replace wins; bare string = append shorthand); defensive on bad config
- tests: 16 cases covering append/replace/shorthand/isolation/malformed

Override only affects the platform-hint segment of the system prompt;
SOUL/context/memory tiers and general instructions are unchanged.
2026-06-18 14:28:01 -07:00
kshitij 2fa16ec2d2
Merge pull request #48529 from kshitijk4poor/salvage-48372-eap
fix(install): relax EAP=Stop around native git/uv calls + fail-fast on uv venv failure (#48352, salvage of #48372)
2026-06-18 22:17:53 +05:30
kshitijk4poor fd12e59e6b fix(install): fail fast when uv venv genuinely fails under relaxed EAP
PR #48372 relaxes EAP=Stop around the uv venv call so PowerShell 5.1
doesn't mistake uv's 'Using CPython ...' stderr for a terminating
NativeCommandError. But relaxing EAP also means a *genuine* uv venv
failure (exit != 0) no longer aborts on its own — Install-Venv would
continue and print 'Virtual environment ready', and in stage mode
Invoke-Stage would report ok=true, even though no venv was created.

Capture $LASTEXITCODE immediately after the relaxed call and throw on
non-zero (Pop-Location first, matching the function's other exit paths),
so the venv stage fails fast instead of falsely succeeding. This is the
explicit guard originally proposed in #48463 (devorun), composed on top
of #48372's reusable helper + regression test.

Adds a regression test asserting the uv venv exit-code capture + throw.
2026-06-18 22:11:35 +05:30
kshitij 4af16b5da2
Merge pull request #48206 from ehz0ah/fix/openviking-current-api-rebased
fix(openviking): adapt memory provider for current api
2026-06-18 21:53:42 +05:30
Teknium 41babc702e chore(release): map iamlukethedev to AUTHOR_MAP 2026-06-18 08:53:31 -07:00
kshitij b39ec2fc37
Merge pull request #48341 from xxxigm/fix/install-ps1-powershell-host-resolution
fix(install): resolve PowerShell host instead of bare `powershell` for uv install
2026-06-18 21:09:50 +05:30
teknium1 ef4b897a18 chore(release): map srojk34 author email 2026-06-18 05:55:17 -07:00
Tranquil-Flow 67316fdc94 fix(install): relax native stderr handling in install.ps1 (#48352) 2026-06-18 12:06:29 +02:00
xxxigm a14bae6bcc fix(install): resolve PowerShell host instead of bare `powershell` for uv
The Windows installer's Install-Uv spawned the astral uv installer with a
hardcoded bare `powershell -ExecutionPolicy ByPass -c "irm .../uv | iex"`.
That name resolves only to Windows PowerShell, and only when its System32
directory is on PATH. Run under PowerShell 7+ (`pwsh`) — or any session where
`powershell` isn't on PATH — the spawn dies with "The term 'powershell' is not
recognized", and uv installation aborts (the installer then appears stuck).

Add Get-PowerShellHostExe, which prefers the absolute path of the host we're
already running in (PATH-independent), then falls back to powershell/pwsh via
Get-Command, then to the bare name. Install-Uv now invokes that resolved exe.
2026-06-18 16:26:34 +07:00
qin-ctx 2a5d51c16e fix(openviking): adapt memory provider for current api
(cherry picked from commit cbb87389f33583518975fbf72671de3fd224bb28)
2026-06-18 16:58:11 +08:00
kshitijk4poor ca28c630c7 chore(release): map infinitycrew39 author email
Add infinitycrew39@gmail.com -> infinitycrew39 to AUTHOR_MAP so the
contributor audit resolves the two cherry-picked commits from the #47945
langfuse trace-scope salvage (merged as #48292) to a GitHub handle instead
of flagging them as an unmapped author email.
2026-06-18 13:09:34 +05:30
Ben Barclay 4440d77bf3
fix(update): scope install-method stamp to the code tree, not $HERMES_HOME (#48188)
The install method (docker/git/pip/...) describes the *running binary*, but
detect_install_method() read it from $HERMES_HOME/.install_method — a shared
DATA directory. The Docker docs deliberately bind-mount $HERMES_HOME
(~/.hermes:/opt/data) so config/sessions/memory persist and can be shared with
a host-side Desktop/CLI install.

When a containerized gateway and a host install share one $HERMES_HOME, the
home-scoped stamp is a single slot describing two installs: the published image
stamps 'docker' on every boot, the host install then reads 'docker' and the
in-app updater refuses to run 'hermes update' ("doesn't apply inside the Docker
container"). Reinstalling the Desktop app from the DMG doesn't help because the
contaminated stamp is re-read every time.

Fix (option 1 — code-scoped stamp):
- detect_install_method() reads <install tree>/.install_method first (next to
  the running code, immune to the shared data dir). It falls back to the legacy
  $HERMES_HOME stamp for back-compat, but IGNORES a 'docker' home stamp when
  not actually containerized — so already-poisoned shared homes self-heal.
- stamp_install_method() writes the code-scoped stamp.
- install.sh stamps $INSTALL_DIR instead of $HERMES_HOME.
- Dockerfile bakes 'docker' into /opt/hermes/.install_method at build time
  (inside the immutable block); stage2-hook.sh no longer writes the home stamp
  and proactively removes a stale 'docker' one to heal existing shared homes.

Genuine containers still resolve to 'docker' (baked stamp, or legacy home stamp
honored when containerized). Unstamped installs in generic containers still fall
through to git/pip (preserves the #34397 fix).
2026-06-18 14:14:41 +10:00
teknium1 c5eb64b9f7 fix(xai): scope native web_search to swap-only + reconcile composer ctx to 200k
Salvage corrections on top of @XVVH's #44341:
- Make native web_search injection a 1:1 swap for an already-present client
  web_search function, NOT an additive grant. The original unconditionally
  appended {"type":"web_search"} on every is_xai_responses turn with any
  tools, force-enabling Grok server-side search even when the user never
  enabled the web toolset (bypassing Hermes web-provider config + tool-trace
  plumbing). Now gated on a client web_search actually being present.
- Reconcile grok-composer context to 200000 (merged in #47908) rather than
  262144; 200k is xAI's published usable context window for Composer 2.5,
  262144 is the /v1/responses input+output budget.
- Update tests to match scoped behavior + add a no-web-toolset guard test.
- AUTHOR_MAP entry for #44341 salvage.

Incomplete-guard (server-side *_call items at in_progress no longer flip
has_incomplete_items) and preflight built-in-tool allowlist kept as-is.
2026-06-17 17:33:32 -07:00
brooklyn! c1f9eb0ec4
fix(desktop): resolve electronDist dynamically + self-heal blocked installs (supersedes #48081/#48082) (#48091)
* fix(desktop): resolve electronDist dynamically + self-heal blocked installs

Supersedes the static-path approach (#48081) and the install-step self-heal
(#48082) with a fix that removes the whole failure class instead of chasing each
symptom. Three distinct faults converged into the June desktop-build outage; this
closes all three.

Root cause (the part #48081 left open — "Gap B"):
  build.electronDist was a static relative path in apps/desktop/package.json, but
  npm workspace hoisting is NOT deterministic — depending on the npm version and
  what else is installed, npm nests the workspace-only electron devDep under
  apps/desktop/node_modules/electron OR hoists it to the repo root. A static path
  matches only one layout, so a clean install intermittently fails with "The
  specified electronDist does not exist". #48081 re-pointed the path at the
  nested layout (correct today) but electron-builder reads electronDist
  STATICALLY, so any future hoist change silently breaks it again — only caught
  by a CI invariant, never self-corrected.

Fix:
- scripts/run-electron-builder.cjs: resolve electron the way Node's runtime does
  — require.resolve("electron/package.json") walks node_modules from the desktop
  project upward and finds electron wherever npm actually put it. The path can
  never drift out of sync with the install layout again, on any OS/npm version.
    * dist present -> pass -c.electronDist=<abs>/dist so electron-builder reuses
      the unpacked runtime (keeps the #38673 fast path that dodges the 26.8.x
      missing-binary re-unpack bug).
    * dist absent  -> omit electronDist; electron-builder fetches Electron itself
      via @electron/get honoring electronVersion + ELECTRON_MIRROR.
  package.json: builder script now runs the wrapper; the static build.electronDist
  is removed (the resolver owns it).
- main.py / install.sh / install.ps1: on a dependency-install failure where the
  electron package staged but its dist is missing (electron's install.js
  process.exit(1) on a blocked/throttled binary download — #47266/#47917/#48021),
  repopulate the dist via electron's downloader (canonical, then npmmirror.com)
  and CONTINUE to the build instead of aborting. npm runs postinstall LAST, so
  the only casualty is electron/dist; bailing here is what made the pack-time
  mirror self-heal unreachable on a blocked network. Hard-fail only when electron
  never staged at all (a genuine dependency error).
- The pack-time mirror fallback now retries the build even when the pre-fetch
  can't populate the dist: the wrapper lets electron-builder download Electron
  itself via the mirror, so the retry is no longer a no-op (it was, when
  electronDist was a static path).

The exact 40.10.2 pin (already on main) keeps the third mode — the native
@electron-internal/extract-zip win32 binding that 40.10.3/40.10.4 ship without a
published prebuild — from recurring.

Tests:
- test_desktop_electron_pin.py: replace the static-path-matches-lockfile
  invariant with contracts that there is no hardcoded electronDist to drift, the
  builder script routes through the resolver, and the resolver uses Node module
  resolution + injects -c.electronDist.
- test_gui_command.py: install-failure self-heal continues to build; genuine
  (electron-never-staged) install failure still hard-fails; pack retries under
  the mirror even when the pre-fetch is blocked.

Salvages/supersedes the overlapping community work in #48003 (sitkarev),
#48012 (omegazheng), #48033 (james47kjv), and #48082.

Co-authored-by: sitkarev <59806492+sitkarev@users.noreply.github.com>
Co-authored-by: omegazheng <zheng@omegasys.eu>
Co-authored-by: james47kjv <220877172+james47kjv@users.noreply.github.com>

* fix(desktop): narrow Electron self-heal to real missing-dist failures

Follow-up on #48091 to remove the remaining misdiagnosis risk from the
installer/build fallback path (#46785 concern): only take the Electron
repair/retry path when Electron's package files are staged and dist is actually
missing/corrupt.

- main.py: add _electron_pkg_staged_missing_dist() and use it to gate install
  failure recovery; fail fast for unrelated npm install errors.
- main.py/install.sh/install.ps1: run cache purge + retry only when dist is
  missing; do not retry unrelated tsc/vite/build failures under an
  Electron-specific narrative.
- install.sh/install.ps1: tighten install-stage self-heal guard to require both
  package.json + install.js and missing dist.
- tests: add coverage that install failure hard-fails when Electron dist already
  exists, and update retry test to reflect the tightened recovery condition.

Validation:
- Python tests: 64 passed
- install.sh-related tests included in the run
- Real mac build on this machine:
  - npm ci at repo root: success
  - cd apps/desktop && npm run pack: success
  - electron-builder packaged darwin arm64 and used custom unpacked Electron dist

* refactor(desktop): trim electron self-heal helpers and comments

Deduplicate mirror-retry into _try_redownload_electron_dist / shell
counterparts; shorten wrapper and install-script commentary without
changing recovery semantics.

---------

Co-authored-by: sitkarev <59806492+sitkarev@users.noreply.github.com>
Co-authored-by: omegazheng <zheng@omegasys.eu>
Co-authored-by: james47kjv <220877172+james47kjv@users.noreply.github.com>
2026-06-17 18:48:35 -05:00
Teknium f8098c6b6f
fix(desktop): resolve electronDist to the actual electron install location (#48081)
After the June lockfile regeneration (#46652) floated electron and reshuffled
npm workspace hoisting, the desktop pack fails with "The specified electronDist
does not exist". apps/desktop/package.json pointed electronDist at the repo
root (../../node_modules/electron/dist) while npm now installs electron nested
under apps/desktop/node_modules/electron. The two contradict, so a clean
install can never package the app (Windows + macOS).

- electronDist -> node_modules/electron/dist (resolved relative to apps/desktop,
  i.e. the workspace-local install npm actually produces).
- hermes_cli/main.py, scripts/install.sh, scripts/install.ps1: add a runtime
  electron-dir resolver that prefers apps/desktop/node_modules/electron and
  falls back to the root hoist, so dist checks + the mirror re-download work
  under either npm layout.
- patch-electron-builder-mac-binary.cjs: try the workspace-local Electron.app
  before the root hoist in the macOS binary-restore fallback (sibling site no
  PR touched).
- test: assert build.electronDist resolves to where the lockfile installs
  electron, so a future hoist change (root <-> nested) can't silently break it.

Salvages the overlapping work in #48003 (sitkarev), #48012 (omegazheng), and
#48033 (james47kjv).

Co-authored-by: sitkarev <59806492+sitkarev@users.noreply.github.com>
Co-authored-by: omegazheng <zheng@omegasys.eu>
Co-authored-by: james47kjv <220877172+james47kjv@users.noreply.github.com>
2026-06-17 18:08:01 -05:00
Austin Pickett fd674af47f
fix(photon): preserve text in mixed iMessage attachments (salvage #46513) (#46818)
* fix(photon): preserve text in mixed iMessage attachments

When an iMessage bubble carried both text and an attachment, spectrum-ts'
inbound mapper returned only buildAttachmentMessage(...), dropping the user's
typed text before Hermes could see it. The Photon adapter then had no 'group'
content path, so the text was lost entirely.

- adapter.py: handle a new 'group' content type that flattens text + attachment
  items, preserving the typed text alongside cached media (extracted shared
  _normalize_binary_payload helper).
- sidecar: emit 'group' content in normalizeContent, and ship
  patch-spectrum-mixed-attachments.mjs which patches spectrum-ts' pinned mapper
  (at npm postinstall AND at sidecar startup, so existing installs self-heal).

Windows robustness fixes on top of the original PR:
- The patcher's CLI guard used 'import.meta.url === file://${argv[1]}', which
  never matches on Windows (file:/// + drive letter) — it silently no-opped.
  Switched to pathToFileURL(argv[1]).href.
- The patcher matched \n-joined strings, so a CRLF checkout (Windows git
  autocrlf) defeated every replacement. It now normalizes CRLF->LF for matching
  and restores the original EOL style on write.

Co-authored-by: Yuhang Lin <yuhanglin@YuhangdeMac-mini.local>

* chore: map YuhangLin contributor email for attribution (#46513)

---------

Co-authored-by: Yuhang Lin <yuhanglin@YuhangdeMac-mini.local>
Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
2026-06-17 16:14:24 -05:00
teknium1 aa6f77596b chore: add AUTHOR_MAP entry for #47904 salvage 2026-06-17 09:49:46 -07:00
teknium1 cc9f37e77c chore: map Rivuza to AUTHOR_MAP for #44249 salvage 2026-06-17 09:49:39 -07:00
teknium1 49ef0241eb chore(release): map Adolanium author email for PR #44628 salvage 2026-06-17 05:40:15 -07:00
xxxigm d1ecebcbfd
fix(desktop): re-download Electron binary via mirror when pack fails (#47266) (#47276)
* fix(desktop): re-download Electron binary via mirror when pack fails (#47266)

Since #38673 pinned build.electronDist to node_modules/electron/dist,
electron-builder reads the Electron binary straight from there and never
downloads it during `npm run pack`. That dist tree is only produced by the
electron package's postinstall (install.js) during `npm ci`. When that
download is blocked or throttled (GitHub's release host is unreachable in
some regions), the dist is missing and the build dies with:

    The specified electronDist does not exist: .../node_modules/electron/dist

The existing ELECTRON_MIRROR fallback in all three desktop-build paths
(scripts/install.ps1, scripts/install.sh, and `hermes desktop` in
hermes_cli/main.py) re-ran `npm run pack` with ELECTRON_MIRROR set — but
pack never downloads Electron anymore, so the mirror was never used and the
retry re-read the same missing dist. The fallback was effectively dead.

Drive the mirror through electron's own downloader instead:

- Add a dist-presence check + a downloader helper (Test-ElectronDist /
  Restore-ElectronDist, _electron_dist_ok / _restore_electron_dist,
  _electron_dist_ok / _redownload_electron_dist) that wipes a partial dist
  + the path.txt version marker (electron's install.js short-circuits on it)
  and re-runs `node install.js`, optionally via a mirror.
- On the first retry, repopulate a missing dist from the canonical source;
  on the mirror retry, re-fetch through npmmirror.com, then pack.
- Gate the re-download on the dist check so an unrelated build failure
  (tsc/vite) doesn't trigger a pointless ~200 MB refetch, and skip the final
  pack when the binary still can't be fetched instead of failing the same way.

* test(desktop): cover Electron dist re-download mirror fallback (#47266)

Add behavior coverage for the electronDist re-download fix:

- _electron_dist_ok across linux/win32/darwin, including the partial-dist
  case (dir present but binary missing) that makes the pinned electronDist
  fail.
- _redownload_electron_dist: no-op when the binary is present, bail when
  install.js is absent, wipe a stale dist + path.txt marker and run
  electron's downloader with ELECTRON_MIRROR injected, and report failure
  when the download still produces no binary.
- `hermes desktop`: the mirror fallback now drives electron's own downloader
  before re-running pack, and skips the final pack entirely when the binary
  can't be fetched.

Replaces the old mirror test that asserted the (now-fixed) dead behavior of
re-running `npm run pack` with ELECTRON_MIRROR set — pack never downloads
Electron under the pinned electronDist, so that retry could never help.
2026-06-16 15:40:55 -05:00
teknium1 7493de7fc3 test(model-switch): cover section-3 no-auth probe; map chimpera author
Salvage follow-up for PR #29575: add regression tests for the section-3
no-api_key /v1/models probe (probes bare endpoints, skips when explicit
models set) and add the contributor AUTHOR_MAP entry.
2026-06-16 13:07:52 -07:00
teknium1 3f80bcac56 chore(release): AUTHOR_MAP entry for x1erra (Sierra) 2026-06-16 13:04:20 -07:00
teknium1 db01910e3a chore(release): map cyb0rgk1tty noreply email for AUTHOR_MAP
Salvage follow-up for PR #46921 — CI matches contributor authorship on the
commit email, which is the GitHub noreply form.
2026-06-16 13:04:07 -07:00
teknium1 cf52370253 chore(release): AUTHOR_MAP entry for Joe Rinaldi Johnson 2026-06-16 10:37:21 -07:00
teknium1 925b0d1ab5 chore: add zimigit2020 to release AUTHOR_MAP 2026-06-16 06:23:53 -07:00
teknium1 1ac76a9472 chore: add MrDiamondBallz to release AUTHOR_MAP 2026-06-16 05:56:11 -07:00
ethernet 39f479cba8
Merge pull request #46085 from xxxigm/fix/bundled-node-global-npm-path
fix(install): make `npm install -g` packages reachable on PATH
2026-06-15 15:47:54 -04:00
Brooklyn Nicholson c23a2eec15 chore: map salvaged contributor email for attribution (#38673) 2026-06-15 13:53:23 -05:00
Austin Pickett 5f6be7f31b
fix(teams): package Microsoft Teams SDK as an installable extra (salvage #43945) (#46764)
* fix(teams): package Microsoft Teams SDK as an installable extra

The Teams adapter imports the microsoft-teams-apps SDK, but it was never
declared as a dependency, so source/local installs hit ImportError and the
adapter silently reported the SDK as unavailable. Add a 'teams' extra
(microsoft-teams-apps==2.0.13.4 + aiohttp) and document 'uv sync --extra teams'.

Per the 2026-05-12 [all] policy, opt-in messaging-platform SDKs are NOT added
to [all] (they would break every fresh install on a quarantined release); the
teams extra is installed on demand like the other platform backends.

Co-authored-by: rio-jeong <rio.jeong@thebytesize.ai>

* chore: map rio-jeong contributor email for attribution (#43945)

* feat(teams): lazy-install the Teams SDK on demand (parity with other channels)

The teams extra alone left Teams as the only messaging platform that wouldn't
auto-install its SDK — every other channel (telegram, discord, slack, matrix,
dingtalk, feishu) lazy-installs via tools.lazy_deps on first connect. Bring
Teams to parity:

- Add 'platform.teams' to LAZY_DEPS (microsoft-teams-apps + aiohttp).
- Replace the passive 'check_teams_requirements = check_requirements' alias with
  a real lazy-installer that calls ensure_and_bind('platform.teams', ...),
  rebinding all Teams SDK globals on success (mirrors check_slack_requirements).
- Call check_teams_requirements() at the top of TeamsAdapter.connect() so
  enabling Teams installs the SDK on demand.
- Keep the passive check_requirements() as the registry check_fn so 'gateway
  status' probes never trigger a pip install.

The 'teams' extra remains for packagers / explicit 'uv sync --extra teams'.

Tests: rework the alias test into shortcircuit + lazy-install assertions, and
update test_connect_fails_without_sdk to simulate an uninstallable SDK.

---------

Co-authored-by: rio-jeong <rio.jeong@thebytesize.ai>
Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
2026-06-15 14:35:15 -04:00
Austin Pickett 0bbff1fc7e
fix(deps): declare websockets as core dep + relax dev setuptools pin (salvage #45486, #44693) (#46744)
* fix: declare websockets as a core dependency

* fix(deps): relax dev setuptools pin 82.0.1 -> 81.0.0 (torch caps setuptools<82)

torch >= 2.11 publishes Requires-Dist: setuptools<82, so any environment
that resolves the dev extra together with torch is unsatisfiable:

    $ uv pip install --dry-run ".[dev]" "torch==2.12.0"
    x No solution found when resolving dependencies:
      ... torch==2.12.0 and all versions of hermes-agent[dev] are incompatible.

81.0.0 is the latest release under the cap and stays inside the declared
build-system window (setuptools>=77.0,<83). uv.lock regenerated with
'uv lock'; diff is scoped to the setuptools entry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: map salvaged contributor emails for attribution

Add AUTHOR_MAP entries for the two cherry-picked contributors so the
check-attribution CI gate passes:
- yehaotian@xuanshudeMac-mini.local -> ArcanePivot (#45486)
- dbeyer7@gmail.com -> benegessarit (#44693)

---------

Co-authored-by: 玄枢 <yehaotian@xuanshudeMac-mini.local>
Co-authored-by: David Beyer <dbeyer7@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
2026-06-15 12:44:44 -04:00
Teknium 3e7e9b24d4 fix: harden salvaged session and browser improvements
Polish salvaged contributor work before PR review:
- read browser inactivity timeout from config with documented fallback
- skip redundant v10 trigram backfill before v11 FTS rebuild
- show delegate_task goals safely in progress previews
- show gateway status model/context without redundant token wording
- wire gateway /sessions to shared session-listing helpers
- map Ravenwolf author emails for release attribution

Co-authored-by: Wolfram Ravenwolf <github.com@wolfram.ravenwolf.de>
Co-authored-by: Amy Ravenwolf <amy@ravenwolf.de>
2026-06-15 07:46:34 -07:00
Teknium 49e743985a fix: route minimax m3 reasoning controls through profile
Follow up PR #46609's api.minimax.io reasoning report by moving the behavior out of the broad run_agent host gate and into the MiniMax provider profile. Only MiniMax-M3 on the documented OpenAI-compatible /v1 route gets reasoning_split/thinking/reasoning_effort; Anthropic-format MiniMax and non-M3 models keep their existing wire shapes.

Co-authored-by: goku94123 <gooku94123@gmail.com>
2026-06-15 07:08:43 -07:00
Teknium 733472952a fix: complete cron jobs lock salvage
Route curator rollback through the same cross-process cron job lock, make save_jobs lock for legacy direct callers without deadlocking nested mutation paths, and harden the regression test so a second _jobs_lock caller really blocks across processes.
2026-06-15 06:29:00 -07:00
FT_IOxCS 92a456f711 fix(cli,deps): clear esbuild audit loop
Upgrade the Vite/esbuild surfaces that kept web, ui-tui, and the bootstrap installer on vulnerable esbuild versions, regenerate the root lockfile, and preserve intentional package+lock dependency edits during update lockfile cleanup.
2026-06-15 06:18:27 -07:00
Teknium 0d82060c74 fix: harden WhatsApp target alias salvage
Add a parser-only routing regression that proves raw WhatsApp group JIDs bypass channel-directory resolution and home-channel fallback, include channel_aliases.json in quick state snapshots, harden malformed alias handling, and map Keiron McCammon for release attribution.
2026-06-15 05:51:47 -07:00
Teknium c17469cb19 chore: map Veritas-7 release attribution
Add the contributor noreply email used by the salvaged xAI OAuth refresh-skew commit so release notes credit the original author.
2026-06-15 05:40:23 -07:00
Teknium aab2e99bae test: cover request debug dump redaction
Keep request dump writes on the shared atomic JSON path, add regression coverage for request body/error/stdout redaction, and map the salvaged contributor email for release attribution.
2026-06-15 05:31:21 -07:00
Teknium f79b109f4f chore: map 0xneobyte release author 2026-06-15 05:25:07 -07:00
kshitijk4poor 1227007aed chore: map capt-marbles contributor email for attribution
Salvaged commit in this PR is authored by capt-marbles
(andrewdmwalker@gmail.com), a bare gmail that does not auto-resolve in
the check-attribution job. Add the AUTHOR_MAP entry.
2026-06-15 17:09:27 +05:30
Teknium aca11c227e
fix(docker): skip gateway reconciliation in dashboard container (autodetect) (#46293)
* fix(docker): skip per-profile gateway reconciliation in dashboard container

When gateway and dashboard containers share a bind-mounted HERMES_HOME,
both run the cont-init.d profile reconciliation script, which creates
s6-log processes for every persisted profile.  These s6-log processes
in different containers race to flock() the same log-directory lock
files under logs/gateways/<profile>/lock, producing repeated
"s6-log: fatal: unable to lock ... Resource busy" errors and a
supervision restart storm.

Add HERMES_SKIP_PROFILE_RECONCILE env var support to container_boot.py
and set it in the official docker-compose.yml dashboard service so the
dashboard container no longer creates per-profile gateway s6 services
it never uses.

* chore(release): map salvaged contributor

* refactor(docker): autodetect dashboard container instead of env-var gate

Replace the HERMES_SKIP_PROFILE_RECONCILE env var with PID 1 argv role
detection. A dashboard-only container never spawns or supervises
per-profile gateways, so the reconcile boot hook now skips itself when
/proc/1/cmdline is the dashboard command — no operator flag to set (or
forget in a hand-written manifest, which would reintroduce the s6-log
flock storm this prevents).

- Extract _strip_container_argv_prefix() shared by the legacy-gateway
  and new dashboard detectors (DRY the init/wrapper/hermes peel).
- Add _is_dashboard_container(); gate reconcile main() on it.
- Drop HERMES_SKIP_PROFILE_RECONCILE from code + docker-compose.yml.
- Tests: argv matrix for both roles + main()-level skip/reconcile proof
  and a regression that the removed env var is now inert.

Co-authored-by: 895252509 <895252509@qq.com>

---------

Co-authored-by: zhouxiang <895252509@qq.com>
Co-authored-by: Ben <ben@nousresearch.com>
2026-06-15 20:51:48 +10:00
Teknium c2b7669ad3
fix(s6): clear stale log lock before startup (#46289)
* fix(cli): clear stale s6-log lock file before startup on virtiofs

* chore(release): map salvaged contributor

---------

Co-authored-by: zxcasongs <35259607+zxcasongs@users.noreply.github.com>
Co-authored-by: Ben <ben@nousresearch.com>
2026-06-15 14:10:51 +10:00
Teknium b770967263
fix(s6): persist profile gateway desired state (#46292)
* fix: persist s6 gateway desired state

* chore(release): map salvaged contributor

---------

Co-authored-by: Alfred Smith <alfred@my-cloud.me>
Co-authored-by: Ben <ben@nousresearch.com>
2026-06-15 14:02:10 +10:00
Teknium 61ee2dbfdb
fix(s6): make profile gateway log parent writable (#46291)
* fix(gateway): chown logs/gateways parent so late-added profiles can log

The per-profile log service script created $HERMES_HOME/logs/gateways/
via 'mkdir -p' but only chowned the leaf logs/gateways/<profile>. When
the first log service boots in root context, the gateways/ parent stays
root:root; every profile registered later runs its log service as the
dropped hermes user, 'mkdir -p' fails with EACCES, and s6-log enters a
sub-second fatal crash-loop flooding the container log. The stage2
recursive heal does not catch it either: it is gated on needs_chown,
which is false when the top-level $HERMES_HOME is already hermes-owned.

Two complementary fixes:

- service_manager._render_log_run: chown the gateways/ parent
  (non-recursively) before the leaf chown. Runs on every root-context
  boot, so it also heals volumes already poisoned by older images.
- docker/stage2-hook.sh: seed logs/gateways in the as_hermes mkdir -p
  block; cont-init runs before any service starts, so the parent
  already exists hermes-owned when the first log/run does 'mkdir -p'.

The needs_chown repair loop needs no twin entry: it already chowns
logs/ recursively, which covers logs/gateways.

Fixes #45258

* chore(release): map salvaged contributor

---------

Co-authored-by: tangtaizhong666 <tangtaizhong792@gmail.com>
2026-06-15 13:47:05 +10:00
leo4226 f795513782
fix(windows): kill hermes before recreating venv to release _bcrypt.pyd lock (#45120)
On Windows, native Python extensions such as _bcrypt.pyd are loaded as
DLLs by any running hermes process. When the installer tries to recreate
the venv (Remove-Item -Recurse -Force "venv"), Windows denies the delete
because the DLL is still mapped into the running process.

Add a taskkill /F /T /IM hermes.exe call before the Remove-Item so any
hermes process tree is stopped first, releasing the file lock. A short
sleep gives the OS time to unload the image before deletion proceeds.

This mirrors the existing force_kill_other_hermes() guard already present
in the --update flow (update.rs), applying the same pattern to the full
reinstall/repair path through install.ps1.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 02:27:00 +00:00
Teknium 2c174bce24 fix(gateway): preserve new input on interrupted replay cleanup 2026-06-14 05:10:39 -07:00
Teknium 0f3670ba79 chore(release): map Diyoncrz18 author email 2026-06-14 04:52:54 -07:00
xxxigm 1db8f7ea80 fix(install): repair existing managed-Node global prefix on re-run
The initial fix only wrote the prefix npmrc on a fresh Node install, so
pre-existing bundled-Node installs (Node already present) were not repaired
by re-running the installer — install_node/ensure_node skip when Node is
already up to date.

Extract the redirect into an idempotent helper
(configure_managed_node_npm_prefix / _nb_configure_npm_prefix) that no-ops
when there's no Hermes-managed npm, and call it unconditionally from
check_node (install.sh) and at the top of ensure_node (node-bootstrap.sh).
Re-running the install command now repairs an affected install in place,
not just brand-new ones.
2026-06-14 17:34:11 +07:00
Aldo 293c04fef6 fix(gateway): suppress exact silence tokens without mutating history 2026-06-14 03:25:08 -07:00
xxxigm a4ee1f223d fix(install): make `npm install -g` packages reachable on PATH
When the installer falls back to a bundled Node under $HERMES_HOME/node,
npm's default global prefix is that Node dir, so `npm install -g <pkg>`
drops the package binary in $HERMES_HOME/node/bin. Only node/npm/npx are
symlinked into the command link dir (~/.local/bin, /usr/local/bin, or
$PREFIX/bin) — so user-installed global package binaries are NOT on PATH
and can't be run, even though `npm i -g` reports success. They also get
wiped on every Node upgrade (the dir is rm -rf'd and re-extracted).

Redirect the bundled Node's npm global prefix to the command link dir's
parent, so global bins land in the link dir (already on PATH, alongside
node/npm/npx) and survive Node upgrades. Scoped to the bundled Node via
its prefix-local global npmrc ($HERMES_HOME/node/etc/npmrc), so the user's
other Node installs and their ~/.npmrc are untouched. Hermes's own global
installs (agent-browser) pass an explicit --prefix and are unaffected.
2026-06-14 17:21:20 +07:00
Teknium 9f33d673e9 fix(tui): persist resumed profile cwd updates to profile db 2026-06-14 02:15:33 -07:00
Teknium af1995a838 chore(release): map chromalinx noreply author 2026-06-13 21:14:32 -07:00
Teknium 4026f526d5 chore(release): map MaxFreedomPollard author email 2026-06-13 15:01:42 -07:00
achaljhawar 819def44c7 fix(agent): scope Nous tags to Nous auxiliary calls 2026-06-13 13:24:40 -07:00
kshitijk4poor 28902dc890 chore: map liuhao1024 contributor email for attribution 2026-06-13 23:39:49 +05:30
Teknium d146b85173 chore(release): map WompaJango author 2026-06-13 07:33:58 -07:00
Teknium 1185dfd773 test: cover legacy Office document extensions 2026-06-13 07:18:37 -07:00
Teknium 6f43ff5572 chore(release): map Gemini schema contributor 2026-06-13 06:12:52 -07:00
Teknium 39a35b784f chore(release): map custom provider resume contributors 2026-06-13 05:51:05 -07:00
xxxigm 573b964dc7 fix(installer): clear an unmerged git index before stashing on update
When an existing install at $INSTALL_DIR has an unmerged index (files in a
"needs merge" state left by a previously interrupted update), the update path
ran `git stash` then `git checkout <branch>`. On a conflicted index `git stash`
aborts with "could not write index" and `git checkout` then aborts with "you
need to resolve your current index first" — surfacing to desktop/bootstrap
users as `git checkout main failed (exit 1)` and failing the whole install at
the repository stage.

Mirror the `hermes update` Python path (#4735): detect unmerged entries with
`git ls-files --unmerged` and clear the conflict state with `git reset` before
stashing. Working-tree changes are still captured by the subsequent stash, so
nothing is discarded; only the index-level conflict markers are dropped, which
lets the checkout proceed.

Fixed in both installers (install.sh and install.ps1) so the Windows GUI
installer and the POSIX one share the same recovery behavior.
2026-06-13 05:19:44 -07:00
Kennedy Umege 311ff967de review: validate refresh_token, path-agnostic recovery log, map author email
Addresses PR review feedback:
- Validate refresh_token (not only access_token) before persisting the
  re-imported Codex token, so a half-token payload can't silently break the
  next refresh cycle.
- Make the recovery log path-agnostic ("Codex CLI auth.json") since
  _import_codex_cli_tokens can read $CODEX_HOME, not only ~/.codex.
- Add regression test: relogin-required + imported token missing refresh_token
  -> re-raise and persist nothing.
- Map kenmege@yahoo.com -> Kenmege in scripts/release.py AUTHOR_MAP
  (fixes the check-attribution job).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 05:15:26 -07:00
Black-Kylin 202e318cb1 fix(gateway): sync compression session splits before failures
Salvages PR #25747 by preserving gateway session rotation even when a post-compression model call fails before returning final content.

Co-authored-by: Hermes <127238744+teknium1@users.noreply.github.com>
2026-06-13 04:51:59 -07:00
Teknium fca84fe20b test: regression guard for Nous 429 fallback re-entry; AUTHOR_MAP entry 2026-06-12 12:21:29 -07:00
Teknium c2326bc3be chore: add itsflownium to AUTHOR_MAP 2026-06-12 12:02:41 -07:00
ethernet 4d68984ec7 fix(tests): remove no-longer-needed forensics 2026-06-12 13:42:42 -04:00
ethernet 2f9d18711f fix(ci): remove pytest-timeout, use per-file timeout only
fix(ci): write a new cache for test durations every time
change(ci): rip out error 4 retries because we found the real bug
2026-06-12 13:42:42 -04:00
Teknium 8b2a3c9c51 chore: add kdunn926 to AUTHOR_MAP 2026-06-12 01:07:50 -07:00
kshitijk4poor e4c168b1f4 chore: map bcsmith528 contributor email for attribution 2026-06-12 10:39:05 +05:30
Austin Pickett 2ee69d0579
fix(skills): let ClawHub index build walk past the 12s browse budget (#44500)
The deploy-site skills index crawl was capped at ~3k ClawHub entries
because CATALOG_WALK_BUDGET_SECONDS applied to max_items=0 walks too.
Only enforce the wall-clock budget for bounded browse requests and pass
limit=0 from build_skills_index so CI walks the full catalog.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 18:03:11 -04:00
teknium1 08b1c44a53 fix(discord): extend bot-task cancellation to connect()'s generic exception branch
Follow-up to #44389: the generic 'except Exception' branch in connect()
had the same orphaned-task hazard as the timeout branch. Extract the
cancel-and-await logic into _cancel_bot_task() and call it from all
three sites (timeout branch, exception branch, disconnect()).

Also adds deaneeth to AUTHOR_MAP.
2026-06-11 12:09:18 -07:00
Teknium 3edd09a46f
fix(whatsapp): restart stale bridge processes instead of silently reusing them (#44205)
A long-lived Baileys bridge survives gateway restarts AND hermes update:
connect() adopted any bridge already listening with status connected, and
disconnect() only kills bridges the adapter spawned itself. Users who
updated to get inbound media support kept talking to a bridge process
serving months-old bridge.js — images and voice notes still arrived as
placeholders with no cached file path (refs #19105 follow-up reports).

Three fixes in the same stale-bridge class:

- Staleness handshake: bridge.js reports a sha256 self-hash in /health
  (scriptHash); connect() compares it against bridge.js on disk and
  restarts the bridge on mismatch. Pre-handshake bridges report no hash
  and are treated as stale, so every existing stale bridge gets recycled
  exactly once on the next gateway start.
- npm dep refresh: deps reinstall when package.json changes (stamp file
  in node_modules), not only when node_modules is missing — a Baileys
  pin bump now actually lands.
- Cache-dir passthrough: the gateway passes profile-aware
  HERMES_{IMAGE,AUDIO,DOCUMENT}_CACHE_DIR to the bridge instead of the
  bridge hardcoding ~/.hermes/image_cache etc., fixing media paths under
  HERMES_HOME overrides, profiles, and the new cache/ layout.
2026-06-11 03:47:29 -07:00
Teknium 85503dceca
Merge pull request #44038 from NousResearch/hermes/hermes-fb4ee8ce
fix(cli): show quick commands in /help output
2026-06-11 03:04:30 -07:00
kshitij c94e93a648
Merge pull request #44084 from kshitijk4poor/salvage/windows-winget-stale-reg
fix(install/windows): repair stale winget registration + refresh/merge PATH after every package manager
2026-06-11 00:25:15 -07:00
kshitijk4poor 9662b76d59 fix(install/windows): merge PATH in Update-ProcessPathForPackages instead of overwriting
Follow-up to the winget stale-registration fix. Update-ProcessPathForPackages
rebuilt $env:Path wholesale from the persisted User+Machine hives (plus winget's
Links dir), discarding any process-only PATH entries added earlier in the
installer run. Since the helper now runs after every package manager, that
wholesale replace is more likely to clobber a process-local entry than the
original winget-branch-only version was.

Merge instead: seed from the current process PATH, then append hive and
winget-Links entries not already present, with a case-insensitive,
order-preserving dedupe. Behaviour on a clean box is unchanged (the hive entries
are simply appended); the difference is that pre-existing process-only entries
now survive the refresh.
2026-06-11 12:49:58 +05:30
xxxigm 899acfe42f fix(install/windows): repair stale winget registration; refresh PATH after every package manager
When ripgrep/ffmpeg is missing, `winget install <id>` on a package winget
already has registered is treated as an upgrade: it finds no newer version and
exits 0x8A15002B (-1978335189, APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE)
without ensuring the binary is actually present. The installer only logged that
code and judged success by `Get-Command rg`, so a stale registration (files
removed outside winget, or a missing alias shim) became a permanent dead-end —
winget kept reporting "already installed" and the user could never reinstall.

Detect that exit code and retry once with `--force` to repair the registration
so the shim reappears.

Also refresh the process PATH after the choco and scoop fallbacks (not just
winget) via a shared helper, so a successful fallback install — or any install
on a box without winget — is no longer misreported as "not installed".
2026-06-11 12:47:59 +05:30
kshitijk4poor 5e81113d09 chore: map dschnurbusch contributor email for attribution 2026-06-11 12:34:12 +05:30
Teknium b8e2c16579
Merge origin/main into salvage branch (resolve AUTHOR_MAP conflict) 2026-06-10 23:25:54 -07:00
teknium1 cb2c13055e fix(gateway): scrub _HERMES_GATEWAY from POSIX detached restart watcher too
Follow-up to the salvaged #41264 (Windows watcher): the setsid/bash detached
restart watcher on Linux/macOS inherits _HERMES_GATEWAY=1 the same way, so
the CLI's self-restart loop guard silently refuses 'hermes gateway restart'
and the gateway never comes back. Scrub the marker from the watcher env on
the POSIX branch as well, and extend the setsid test to assert it.
2026-06-10 23:22:43 -07:00
Teknium 2450fd7066
chore: add mvanhorn to AUTHOR_MAP 2026-06-10 22:56:17 -07:00
Brooklyn Nicholson 419c8a98a9 Merge remote-tracking branch 'origin/main' into hermes/salvage-composer-drafts 2026-06-11 00:07:07 -05:00
teknium1 68ffedb6a9 chore(release): map Spaceman-Spiffy for #35586 salvage 2026-06-10 20:45:16 -07:00
Teknium 3d14f01fd6 fix(desktop): debounce per-keystroke draft persistence writes
The salvaged draft-persistence effect wrote to localStorage on every
keystroke — the composer's per-keystroke path was deliberately slimmed
down previously, so debounce the write (400ms) and flush pending text on
scope change/unmount so a fast session switch can't drop trailing
keystrokes. Also add AUTHOR_MAP entry for the salvaged commit.
2026-06-10 22:34:30 -05:00
Teknium 0a5762c78d fix(web): genericize free-MCP client identity per telemetry policy
Replace the hermes-identifying clientInfo/User-Agent/session-id prefix on
the keyless Parallel Search MCP path with a neutral 'mcp-web-client'
identity. Project policy forbids third-party usage attribution without an
explicit user opt-in (see telemetry PR policy); MCP requires a clientInfo,
so a generic one satisfies the spec without attributing traffic.

Also adds the contributor AUTHOR_MAP entry and refreshes uv.lock against
current main (parallel-web 0.6.0).
2026-06-10 19:54:38 -07:00
teknium1 2d75833abe chore(release): map ianculling for #36087 salvage 2026-06-10 12:39:44 -07:00
brooklyn! 6de3963e37
fix(desktop): keep model runtime state per session (#43702)
* fix(desktop): keep model runtime state per session

(cherry picked from commit f72ee87d99ee38cb7b5badeb9a8af869bb92073a)

* fix(desktop): keep footer model state scoped to active session

(cherry picked from commit d91942ebd4671ff857b5c8526dbf133f04782ecb)

* fix(desktop): restore stored runtime when resuming sessions

(cherry picked from commit 32b3793418257617b8da57e26151f079c2620d00)

* fix(desktop): persist live runtime changes for resume

(cherry picked from commit c58467779436dcef44a80ad55b52664752dc0837)

* fix(desktop): persist resumed endpoint runtime

* chore(attribution): map pinguarmy's commit email in AUTHOR_MAP

The salvaged commits on this branch preserve @pinguarmy's authorship
(郝鹏宇 / peterhao@Peters-MacBook-Air.local). Add the mapping so the
check-attribution CI gate resolves the email to the GitHub username.

---------

Co-authored-by: 郝鹏宇 <peterhao@Peters-MacBook-Air.local>
2026-06-10 18:16:50 +00:00
Teknium 07ac185904
fix(ci): exit-4 forensics for vanishing test files in run_tests_parallel.py (#43646)
* fix(ci): append filesystem forensics when a per-file pytest run exhausts exit-4 retries

A PR-added test file (tests/test_iron_proxy.py, PR #30179) repeatedly
failed exactly one CI shard with 'ERROR: file or directory not found'
across 4 runs (including a fresh merge SHA on fresh runners), while the
identical slice passes locally against the same merge commit and a
tree-integrity watcher confirms no sibling test mutates the repo. Three
unrelated branches showed the same one-shard signature the same day.

We currently cannot attribute these because the log only carries
pytest's exit-4 line. This adds a forensics block to the captured
output when exit-4 survives the retry loop:

- does the file exist NOW (post-retries)
- parent dir entry count + similarly-named entries
- git status --porcelain dirty-entry count + first 10 entries

Zero behavior change: rc stays 4, retries unchanged, forensics wrapped
in a broad try/except so they can never mask the failure.

Two new tests cover the exhausted-retries and genuinely-missing paths.

* chore: drop the two forensics tests — ship the runner change only
2026-06-10 10:04:17 -07:00
teknium1 189ffe7362 test: port voice-reply suffix assertions, fix change-detector cap test, add AUTHOR_MAP entry
- Add output_path suffix assertions (.ogg Telegram / .mp3 non-Telegram) to
  _send_voice_reply tests, covering the OGG voice-note path that landed on
  main in ae82eed2b (the PR's third commit was redundant with it).
- Convert test_gemini_default_is_32000 back to an invariant against
  PROVIDER_MAX_TEXT_LENGTH instead of a hardcoded literal.
- Map barronlroth@gmail.com -> barronlroth in scripts/release.py.
2026-06-10 02:57:39 -07:00
Teknium 888bf96025 chore(release): add tomekpanek to AUTHOR_MAP 2026-06-10 00:34:38 -07:00
teknium1 099146fedd chore: add AUTHOR_MAP entry for PR #33958 contributor 2026-06-10 00:18:11 -07:00
teknium 2ce3ae3d16 fix(error-classifier): don't misclassify unsupported-param 400s as context overflow
A GPT-5 model rejecting max_tokens returns a 400 whose message contains the
literal substring 'max_tokens' — one of the _CONTEXT_OVERFLOW_PATTERNS. The 400
path in _classify_400 checked overflow patterns before any request-validation
check (which only existed on the 5xx path), so the parameter error was routed
into the compression loop, re-sent with the same bad param, and ended in
'Cannot compress further' on a tiny context.

Hoist a request-validation guard (unsupported/unknown parameter) above the
context-overflow check in _classify_400. Deliberately excludes the generic
invalid_request_error code, which OpenAI also stamps on real overflow 400s, so
genuine overflows still compress. Pairs with the max_completion_tokens param
fix that stops the bad request at the source.

Also adds AUTHOR_MAP entry for the salvaged PR #13902 commit.
2026-06-09 23:22:10 -07:00
Teknium ab55008631 chore: add AUTHOR_MAP entry for OndrejDrapalik
Maps the salvaged #36781 commit author email to the GitHub login so the
release attribution + CI author check resolve.
2026-06-09 23:21:24 -07:00
teknium 1967c590ed
chore: add AUTHOR_MAP entry for xiaoxinova
Maps xiaoxingitee@gmail.com -> xiaoxinova so the contributor-attribution
CI check passes when PR #42342 (MiniMax-M3 1M context fix) is merged.
2026-06-09 22:35:38 -07:00
Teknium f082b4ec5c
fix(ci): make parallel runner's exit-4 retry robust for newly-added test files (#42994)
The per-file test runner re-runs a file once when pytest exits 4 ("file or
directory not found") while the file exists on disk — a transient seen on
loaded shared CI runners where the planner collects a file (--collect-only
counts its tests) but the per-file subprocess fails to stat it moments later.

A single immediate retry could land in the same brief high-load window and
fail again, and the retry was gated on one Path.exists() check that can itself
be a flaky stat under that load — so a freshly-added test file that LPT pins to
one shard would deterministically red that shard on every run (no actual test
failure; the file just never executes).

- Extract the subprocess spawn/communicate/process-tree-kill logic into a
  shared _spawn_pytest_once() helper (removes ~90 lines of duplication between
  the primary run and the retry).
- Replace the single-shot retry with a bounded backoff loop
  (_EXIT4_RETRY_ATTEMPTS, escalating sleep) that re-runs while the file is
  present on disk.
- Add _file_present() which re-checks existence across a few spaced stats, so a
  single flaky negative stat doesn't wrongly conclude the file is missing. A
  genuinely-missing file (typo/deleted) still fails fast — exit 4 is not
  swallowed when the file truly does not exist.
- Tests: transient-then-pass recovery, genuinely-missing fails fast with no
  retry, give-up after max attempts, and _file_present transient/missing cases.
2026-06-09 21:39:09 -07:00
xxxigm 02f878ec5a
docs(windows): correct native data dir to %LOCALAPPDATA%\hermes (#42856)
* docs(windows): correct native data dir to %LOCALAPPDATA%\hermes

The Windows-native guide claimed a deliberate split where config, auth,
skills, and sessions live under %USERPROFILE%\.hermes. That is not what
the installer does: scripts/install.ps1 sets HERMES_HOME=%LOCALAPPDATA%\hermes,
so data actually lives in %LOCALAPPDATA%\hermes alongside the disposable
install (the hermes-agent\, git\, node\, bin\ subdirectories) — `hermes
config` confirms config.yaml/.env resolve there, not under %USERPROFILE%.

Update the data-layout table, the "split is deliberate" note, the env-var
and uninstall sections to describe the real layout: data and install share
the %LOCALAPPDATA%\hermes root, reinstall only replaces hermes-agent\, and
a full wipe targets %LOCALAPPDATA%\hermes (with %USERPROFILE%\.hermes kept
only as a legacy/WSL cleanup). Mention HERMES_HOME as the override knob.

* docs(windows): fix PATH + bin layout to match installer

The installer adds hermes-agent\venv\Scripts (where hermes.exe lives) to
User PATH and sets HERMES_HOME — not %LOCALAPPDATA%\hermes\bin. The \bin
dir holds Hermes's managed uv.exe, not a hermes.cmd shim. Correct the
install-step list and the data-layout table accordingly.

* fix(install): show real HERMES_HOME path in setup messages

The native Windows installer wrote config/env/skills under $HermesHome
(%LOCALAPPDATA%\hermes) but its success messages claimed ~/.hermes,
which doesn't exist on native Windows. Print the actual paths so a new
user can find their config, .env, and skills.
2026-06-09 14:11:20 -05:00
brooklyn! 8d71c38919
fix(desktop): rebind sessions after websocket reconnect (salvage of #41740) (#43004)
* fix(desktop): rebind sessions after websocket reconnect

* docs(desktop): explain the reconnect-resume guard in use-route-resume

The reconnect fix turns on two subtle conditions with no inline rationale:
`seenGatewayStateRef` suppresses a spurious "became open" on the first effect
run (so a session mounting with the gateway already open doesn't double-resume),
and the `gatewayBecameOpen ||` arm forces a re-resume even when the route looks
`alreadyActive` because the cached runtime id can be stale after the gateway
rebinds/reaps the session. Comment both so the next reader doesn't "simplify"
them back into the original bug. No behavior change.

---------

Co-authored-by: Josh Dow <josh.dow@prepad.io>
2026-06-09 19:01:00 +00:00
brooklyn! ba44de06da
fix(install): self-heal a stuck Electron download (salvage of #42894) (#42998)
* fix(install): self-heal a stuck Electron download on the desktop build

The desktop build downloads Electron (~114MB) from GitHub. A corrupt cached
zip, or a blocked/throttled GitHub release host (the repeating "retrying" log),
hard-failed the install — and install.sh had no recovery at all while
install.ps1 / `hermes desktop` only purged the cache.

All three build paths now escalate on a failed `npm run pack`:
GitHub → purge corrupt electron-*.zip + stale *-unpacked and retry → one retry
via a public Electron mirror (npmmirror.com). @electron/get SHASUM-verifies the
download, and a user-pinned ELECTRON_MIRROR is always respected (never
overridden). Adds a bash clear_electron_build_cache()/_desktop_pack() to mirror
the existing PowerShell/Python helpers.

* test(install): cover the Electron mirror fallback

Verify `hermes desktop` falls back to a mirror when the cache purge finds
nothing, and that a user-pinned ELECTRON_MIRROR is respected (no extra attempt,
not overridden).

* docs(desktop): troubleshoot a stuck Electron download

Document the automatic cache-purge + mirror fallback, how to pin your own
ELECTRON_MIRROR, and how to clear a corrupt cached zip by hand.

* docs(install): correct the Electron mirror trust framing

The mirror-fallback comments and the desktop troubleshooting doc implied
`@electron/get`'s SHASUM check makes the npmmirror.com download safe against
tampering. It doesn't: the SHASUMS256.txt is fetched from the same mirror, so
the check guards against a corrupt/partial download, not a compromised mirror.

Reframe all four surfaces (install.sh, install.ps1, `hermes desktop`, and the
docs) to state the trust trade-off honestly — npmmirror.com is the de-facto
Electron community mirror, we only fall back to it after the canonical GitHub
download fails, and a user-pinned ELECTRON_MIRROR is never overridden. No
behavior change.

---------

Co-authored-by: xxxigm <tuancanhnguyen706@gmail.com>
2026-06-09 18:19:14 +00:00
Philip D'Souza 92dfd70d6a
fix(photon): production hardening for the gRPC-native iMessage channel (#42732)
* fix(photon): override transitive CVEs in the sidecar deps

`npm audit` flagged 7 high-severity transitive CVEs (protobufjs code injection
GHSA-66ff-xgx4-vchm + outdated @opentelemetry OTLP exporters) pulled in via
spectrum-ts -> @photon-ai/otel. npm's suggested fix downgrades spectrum-ts to a
version that targets the decommissioned spectrum host, so instead pin patched
versions via `overrides` (protobufjs 8.6.1, @opentelemetry/* 0.218.0) without
touching spectrum-ts. `npm audit` -> 0; spectrum-ts + provider still import.

* fix(photon): harden the sidecar bridge + bound the dedup cache

- constant-time sidecar control-token comparison (was `!==`, timing-attackable).
- cap the control-channel request body (2 MiB) so a compromised local peer can't
  OOM the sidecar.
- wrap the inbound gRPC stream consumer in a re-subscribe loop with capped
  exponential backoff + jitter — if the async iterator throws/ends it would
  otherwise stop inbound forever (the adapter dedupes any replay).
- add an unhandledRejection handler so a stray rejection logs instead of killing
  the process.
- dedup cache (adapter) was a true bounded LRU only for expired entries; a burst
  of unique ids within the window grew it without limit. Evict oldest at the cap.

* chore: add AUTHOR_MAP entry for PhilipAD

---------

Co-authored-by: PhilipAD <philipadsouza@gmail.com>
2026-06-09 11:12:58 -04:00
teknium1 be2f739e9a test(desktop): cover sleep/wake session recovery in use-prompt-actions
Adds three vitest cases for the recovery path: resume+retry on
"session not found", no-resume passthrough on other errors, and
no-resume when there is no stored session id. Also maps the
contributor's commit email in release.py AUTHOR_MAP.
2026-06-09 03:16:59 -07:00
teknium1 520b59db16 fix(tui): use canonical get_fallback_chain for parity + map author
Follow-up to the salvaged fallback-chain fix:
- Replace the hand-rolled fallback loader with the shared
  hermes_cli.fallback_config.get_fallback_chain() helper so the TUI path
  matches HermesCLI and gateway/run.py exactly: fallback_providers stays
  first and keeps order, with distinct legacy fallback_model entries
  merged in after (deduped). Previously the TUI loader picked one key OR
  the other, diverging from CLI/gateway when both were set.
- Update the test to assert the merged canonical semantics.
- Add psionic73 to scripts/release.py AUTHOR_MAP (CI gate).
2026-06-08 22:53:42 -07:00
teknium ba622d44e4 chore(release): add AUTHOR_MAP entry for m4dni5 2026-06-08 22:46:57 -07:00
teknium 2c1aaa9cba fix: keep interactive OAuth setup-token inheriting stdin
The blanket DEVNULL pass muzzled run_oauth_setup_token()'s interactive
'claude setup-token' login, which needs inherited stdin to prompt the
user. Revert that one call and replace the guard's brittle file:line
whitelist with an inline 'noqa: subprocess-stdin' marker that travels
with the code.
2026-06-08 22:46:57 -07:00
m4dni5 bddab61bcb ci: add subprocess stdin= regression check for TUI-context code
scripts/check_subprocess_stdin.py scans agent/, tools/, plugins/, and
tui_gateway/ for subprocess.run() and subprocess.Popen() calls that
don't explicitly set stdin=. Missing stdin= means the child inherits the
parent's fd, which in TUI mode is the JSON-RPC pipe — causing gateway
crashes on stdin EOF.

Exits 0 (pass) or 1 (violations found). Can be run manually or added to
CI. Skips comments, docstring references, and calls that use input= (which
creates its own pipe).

Usage: python scripts/check_subprocess_stdin.py
2026-06-08 22:46:57 -07:00
teknium1 3dcfbbfc49 chore(release): add underthestars-zhy to AUTHOR_MAP
Salvage follow-up for PR #42444 — maps the contributor's commit email
so the changelog generator can attribute the Photon gRPC channel work.
2026-06-08 21:03:58 -07:00
teknium1 c3420d91ad chore: add jooray to AUTHOR_MAP for salvaged simplex PR #27978 2026-06-08 21:03:45 -07:00
teknium1 300371c3f2 chore: add AUTHOR_MAP entry for ruangraung (PR #42308 salvage) 2026-06-08 15:53:16 -07:00
Ray Sun 37561c214b fix(photon): use allowlisted device client_id + validate token before save
Photon now allowlists registered device clients on the device-code
endpoint; the old client_id "hermes-agent" is rejected with
400 invalid_client, breaking the entire login flow. Switch to Photon's
published "photon-cli" device client and send the standard scope.

Also validate the device-flow token against /api/auth/get-session and
/api/projects/ before persisting it, and extract token candidates from
every response shape Photon has used (access_token, accessToken,
data.*, set-auth-token header) so a token that authenticates the
session lookup but is rejected by the project API fails loudly at
login instead of 404ing downstream.

Verified live: request_device_code() now returns 200 + a valid
user_code where "hermes-agent" returned 400 invalid_client.

Salvaged from #34467 by @yanxue06.
2026-06-08 15:52:33 -07:00
Teknium 5e9d7a7661
fix(skills-hub): stop shipping a degenerate index when GitHub taps collapse (#42347)
The Skills Hub lost every api.github.com-backed source — the OpenAI,
Anthropic, HuggingFace, NVIDIA, gstack, Claude Marketplace and Well-Known
tabs all vanished — while ClawHub/skills.sh/LobeHub/browse.sh survived. A
GitHub API rate limit during the docs-deploy crawl zeroed all three
api.github.com sources (github / claude-marketplace / well-known) at once.

Two compounding bugs let the broken index reach the live site:

1. build_skills_index.py wrote the output file BEFORE the health check, so
   even when the github floor (30) tripped and the script exited 2, the
   degenerate file was already on disk. deploy-site.yml then swallowed the
   exit code with `|| echo non-fatal` and extract-skills.py read the partial
   index. Fix: run the health check first, write the file only when healthy,
   exit without writing on failure. Removed the non-fatal swallow in
   deploy-site.yml so a collapse fails the deploy and the last good site
   stays live (Pages serves the previous build).

2. The build-time GitHub listing path returned [] on a 403 rate-limit without
   retrying or flagging it, so a rate-limited crawl looked identical to an
   empty source. Fix: a shared _github_get() helper on GitHubSource with
   retry/backoff (honors Retry-After / X-RateLimit-Reset on 403/429, backs
   off on 5xx + transport errors) and flags is_rate_limited. Routed
   _list_skills_in_repo and _fetch_file_content through it; gave
   ClaudeMarketplaceSource a persistent GitHubSource + is_rate_limited so the
   builder can name the rate limit as the cause instead of '0 results'.

Added tests/scripts/test_build_skills_index_health.py pinning both contracts:
a degenerate crawl exits non-zero and writes no file; a healthy crawl writes
the index with github/claude-marketplace/well-known all present.
2026-06-08 15:21:28 -07:00
kshitijk4poor a1cb84aca9 chore(release): add mnajafian-nv to AUTHOR_MAP
Unblocks #41551 (and any future mnajafian-nv contributions) from the
contributor-attribution check. Maps mnajafian@nvidia.com -> mnajafian-nv.
2026-06-09 02:40:43 +05:30
teknium1 754154a9c2 fix(tests): retry per-file pytest subprocess once on exit-4 when the file exists
The parallel test runner sharded a present, tracked test file
(tests/plugins/platforms/photon/test_inbound.py) onto a slice that then
reported 'file or directory not found' (pytest exit 4) at exec time —
even though the planner had just enumerated the file via --collect-only
('5269 passed, 0 failed' in the same run). On loaded shared CI runners
the per-file subprocess can fail to stat a file the planner already saw;
the deterministic LPT slicer then reproduces it on every rerun because
the same file set lands on the same shard.

Fix: when a per-file run exits 4 AND the file still exists on disk, retry
the subprocess once before surfacing it as a hard failure. This kills the
shard-flake class for everyone, not just this PR.

Does NOT widen the exit-5-is-pass rule — exit 4 on a genuinely missing
file still fails (verified). Retry reuses the same pgroup-kill cleanup as
the primary run so no grandchildren orphan.

Validation: photon dir runs green through scripts/run_tests_parallel.py;
unit-level negative case confirms a nonexistent file still returns rc=4.
2026-06-08 13:38:30 -07:00
Brooklyn Nicholson e88116256c fix(update): scope git fetch to target branch
A bare `git fetch origin` (and `git fetch upstream`) pulls every ref. The
repo carries thousands of auto-generated branches, so on any
non-single-branch checkout the installer's update path and `hermes update`
spend minutes downloading the full branch list — long enough to stall the
desktop installer or trip the follow-up `git pull --ff-only`.

Scope every update-path fetch to the branch we actually compare/merge
against:
- scripts/install.sh: collapse the remote to single-branch and fetch only
  $BRANCH on the "existing install, updating" path.
- hermes_cli/main.py: fetch the resolved branch in the apply path, the
  --check path (upstream + origin), and the fork upstream-sync.

Tracking-ref updates still happen via git's opportunistic refspec, so the
later origin/<branch> rev-parse/rev-list checks are unaffected.

Tests assert the apply-path fetch is branch-scoped and never bare.
2026-06-08 15:24:31 -04:00
teknium1 c78b3e1d3c fix(auth): add Codex OAuth accounts as distinct pool entries
hermes auth add openai-codex now creates an independent
manual:device_code pool entry per account instead of routing through
the singleton _save_codex_tokens save path, which collapsed every
added account into the latest login (the second add overwrote the
first account's singleton-mirrored device_code entry). This is the
add-path half of #39236; PR #39243 (already on this branch) fixes the
re-auth half.

manual:device_code entries refresh from their own token pair
(_sync_codex_entry_from_auth_store only adopts the singleton for
source=="device_code"), so they need no providers.openai-codex
shadow. Adding the first credential marks openai-codex active (the
singleton path did this implicitly) so the setup wizard's
get_active_provider() check still passes; subsequent adds leave the
active provider untouched.

Adds SOURCE_MANUAL_DEVICE_CODE constant and a regression test that two
distinct accounts keep distinct token pairs. Updates two existing add
tests to the pool-only behavior.

Co-authored-by: glesperance <info@glesperance.com>
2026-06-08 11:57:03 -07:00
Teknium abcf996b1f
feat(windows): enable dashboard /chat tab via ConPTY (win_pty_bridge) + tests (#42251)
* feat(windows): enable dashboard chat tab via ConPTY (win_pty_bridge)

Add hermes_cli/win_pty_bridge.py — a pywinpty-backed drop-in for
PtyBridge with the same spawn/read/write/resize/close surface — and
wire it into the web_server PTY import block so Windows picks it up
instead of falling back to None.

pywinpty is already a declared win32 dependency (pyproject.toml).
The ConPTY read path runs inside run_in_executor so the event loop
is never blocked. Spawn/read/write/terminate call shapes are taken
directly from tools/process_registry.py which already exercises the
same pywinpty version.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: remove WSL2-only caveat for dashboard chat tab

The chat pane now works on native Windows via the ConPTY bridge added
in the previous commit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(windows): cover ConPTY bridge + web_server platform-branched import

Companion to the bridge added in the previous commits.  Verified live on
native Windows 11 (pywinpty 2.0.15) against `hermes dashboard`'s
`/api/pty` WebSocket: the spawned `hermes --tui` (node entry.js) renders
through ConPTY, resize escapes reach `setwinsize`, and closing the WS
reaps both the node child and the pywinpty agent with zero orphans.

tests/hermes_cli/test_win_pty_bridge.py
  Mirrors the layout of the existing POSIX test_pty_bridge.py:
  spawn/io/resize/close/env coverage against cmd.exe and python -c,
  plus the cross-platform fallback surface (PtyUnavailableError, the
  off-Windows `spawn -> raises PtyUnavailableError` guard, and the
  load-bearing _clamp() helper that protects setwinsize from garbage
  winsize values out of xterm.js).

tests/hermes_cli/test_web_server_pty_import.py
  Asserts that web_server.PtyBridge resolves to WinPtyBridge on win32
  and to the POSIX PtyBridge on POSIX, that PtyUnavailableError is the
  matching class on each side (so isinstance checks in /api/pty's
  spawn fallback path work), and a source-text check that pins the
  platform-branched import shape so a future refactor can't quietly
  collapse it back to a POSIX-only import.

scripts/release.py
  AUTHOR_MAP entries so CI release-note generation can resolve both
  authors' plain (non-noreply) emails to their GitHub logins.

Co-Authored-By: JoelJJohnson <josephjohnson.joel@gmail.com>
Co-Authored-By: Nea74 <andreas@schwarz-ketsch.de>

---------

Co-authored-by: JoelJJohnson <josephjohnson.joel@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Nea74 <andreas@schwarz-ketsch.de>
2026-06-08 11:32:43 -07:00
teknium1 5916248dc0 chore: add AUTHOR_MAP entry for rbrtbn (salvage #25939) 2026-06-08 11:29:53 -07:00
teknium1 8cb1908e18 chore: map paulb26 in AUTHOR_MAP for #24135 salvage 2026-06-08 07:03:12 -07:00
kshitijk4poor f96eb857a5 chore: add kristianvast to AUTHOR_MAP 2026-06-08 15:16:20 +05:30
teknium1 00c46b8ff9 test(tui): cover heapdump opt-in gate + retention; add AUTHOR_MAP
On-disk vitest coverage for the auto-heapdump disk-safety guard: opt-in
gating (suppressed diagnostics-only path), truthy-spelling acceptance,
manual-trigger passthrough, and the retention prune. Test approach
adapted from #21780 (briandevans) and #21822 (LeonSGP43), reconciled to
the merged gate semantics. Maps alarcritty into AUTHOR_MAP for CI.
2026-06-08 02:20:49 -07:00
xxxigm a5c12f5f59 fix(install): move broken checkout aside instead of deleting it
Review feedback (#40998): `rm -rf` / `Remove-Item -Recurse -Force` on the
install dir is destructive -- a user might still want whatever is there.
Rename the broken checkout to a timestamped `<dir>.broken-<ts>` backup and
re-clone fresh, so nothing is ever deleted. Transient cleanup of a clone
attempt that fails within the same run is left as-is.
2026-06-08 02:18:21 -07:00
xxxigm fc0900d120 fix(install): re-clone interrupted (commit-less) checkout instead of failing
An interrupted previous clone leaves the install dir's .git present but with
no initial commit. rev-parse --is-inside-work-tree and git status both still
succeed there, so the installer entered the update path and ran `git stash`,
which aborts with "You do not have the initial commit yet" and failed the
desktop install at the "Cloning Hermes repository" stage.

- install.ps1: add a `git rev-parse --verify HEAD` probe to the repo-validity
  check so a commit-less checkout is treated as broken and re-cloned fresh.
- install.sh: mirror it at the top of clone_repo() — drop a partial checkout
  with no resolvable HEAD so the fresh-clone path handles it (POSIX parity).

Fixes #40998
2026-06-08 02:18:21 -07:00
teknium1 02a4d66951 fix(auxiliary): retry transient transport error once before fallback (#16587)
A one-off transient transport failure (streaming-close / incomplete
chunked read / 5xx / 408) on an auxiliary LLM call escalated straight to
provider/model fallback (or, for context compression, dropped the summary
and entered cooldown), even when an immediate retry on the same provider
would have succeeded.

Add a single same-target retry at the top of call_llm() and
async_call_llm() — before the existing except-chain — gated on a new
_is_transient_transport_error() that reuses the canonical
_is_connection_error() detector plus a 5xx/408 status check. A second
failure (or any non-transient error: auth, other 4xx, malformed payload)
falls through to first_err and the existing fallback handling unchanged.

This lives in call_llm so every auxiliary task (compression, memory flush,
title generation, session search, vision) shares one transient-retry
surface, rather than each caller re-implementing it. The context
compressor needs no change — it calls call_llm and inherits the retry; its
existing fallback-to-main path (#18458) now composes naturally (retry the
aux model once, then fall back to main only if the retry also fails).

Co-authored-by: ARegalado1 <alberto.regalado@ymail.com>
2026-06-08 01:05:45 -07:00
teknium1 d759c13c09 chore(salvage): lint fix + AUTHOR_MAP for desktop source-folders PR #40272
eslint --fix (import sort + padding-line-between-statements) on sidebar/index.tsx
after cherry-picking @dangelo352's commits; add release.py AUTHOR_MAP entry so
CI doesn't block on the unmapped author email.
2026-06-07 23:44:04 -07:00
teknium1 4d926f248d chore(release): add AUTHOR_MAP entry for rodboev 2026-06-07 22:39:51 -07:00
teknium1 39c4ac3af1 chore(release): add AUTHOR_MAP entry for JimStenstrom 2026-06-07 22:30:02 -07:00
teknium1 240c5d4543 chore: map martin.alca@gmail.com -> draix in AUTHOR_MAP
Salvage follow-up for PR #33221 — the cherry-picked commit is authored
under martin.alca@gmail.com (not the draixagent@gmail.com already mapped),
which would fail the CI author-attribution gate.
2026-06-07 22:22:01 -07:00
teknium1 f5bd09af4b refactor(acp): share interrupt-sentinel prefix, simplify guard
Replace the ACP-local prefix/suffix matcher + helper with a single
startswith() check against INTERRUPT_WAITING_FOR_MODEL_PREFIX, now
defined once in conversation_loop.py where the sentinel is produced.
Keeps the source of truth in one place so the guard cannot drift if
the status string changes. Net -17 LOC in server.py.

Also add lsaether to release.py AUTHOR_MAP.
2026-06-07 22:20:43 -07:00
Teknium 2789bf4e25 fix(auxiliary): route Codex Responses path through shared converter (#5709)
The auxiliary Codex adapter maintained its own chat->Responses conversion
loop that forwarded every non-system message's role verbatim into
Responses input[]. When flush_memories()/compression replayed session
history containing assistant tool_calls + role=tool results, those tool
messages leaked into the request and the Responses API rejected them with
HTTP 400: Invalid value: 'tool'.

Route _CodexCompletionsAdapter.create() through the same shared converter
the main agent transport uses (_chat_messages_to_responses_input), so tool
calls become function_call items and tool results become function_call_output
items with a valid call_id. Single conversion path means no future drift.

Also remove the now-dead _convert_content_for_responses() helper — its only
caller was the private conversion loop this change deletes.

Co-authored-by: ProgramCaiCai <techxacm@gmail.com>
2026-06-07 22:18:31 -07:00
teknium1 b5f7a1f299 chore(release): add basilalshukaili to AUTHOR_MAP 2026-06-07 22:09:45 -07:00
teknium1 0c67d4015f chore(release): map islam666 for as-is salvage batch 2026-06-07 21:50:57 -07:00
teknium1 38d1a414a1 chore: add islam666 to AUTHOR_MAP for salvaged PR #39624 2026-06-07 21:50:25 -07:00
teknium1 9c5d1afbe9 chore: add giladbau to AUTHOR_MAP for salvaged PR #20182 2026-06-07 18:05:58 -07:00
teknium1 1c7ae46f0e chore(release): map AlchemistChaos co-author email for #40135 salvage 2026-06-07 17:29:12 -07:00
Enes Aydın f3af489ec2
install.sh: hint at root-owned npm cache when desktop npm install fails (#39688)
When apps/desktop's `npm ci`/`npm install` fails, install_desktop printed a
single "Desktop workspace npm install failed" line and aborted, leaving the
user with a wall of raw npm output. A common trigger is a root-owned ~/.npm
cache left by an earlier `sudo npm`/`sudo npx`: the non-root install then
cannot write the shared cache, and npm reports it as EEXIST / "File exists"
while the real errno is EACCES (-13) -- so it reads like an installer bug.

Add a targeted remediation hint on that failure path pointing at:

    sudo chown -R "$(id -un)" ~/.npm && npm cache verify

followed by the manual rebuild command. The stage stays a hard failure by
design (a silent skip yields a "complete" install with no app); only the
failure output changes.
2026-06-07 17:55:58 +00:00
teknium1 16786f3bb3 feat(desktop+gateway): remote media relay — attach images/PDFs and display gateway images over the network
Desktop connected to a remote gateway can now attach images and PDFs and
display agent-written images. Previously the desktop passed a LOCAL file path
to image.attach; on a remote gateway that path doesn't exist, so the image was
silently dropped ("skipped unreadable path") and the vision model never saw it.
The reverse direction was also broken — images the agent wrote on the gateway
rendered as dead links in the remote client.

Gateway (tui_gateway/server.py):
- image.attach_bytes: base64 byte upload written into the gateway's own images
  dir and queued via the existing native-image-attach pipeline. Magic-byte
  extension sniffing, data-URL prefix + whitespace tolerance, 25 MB cap,
  structured error codes. Accepts content_base64/filename (canonical) and
  data/ext (older-desktop aliases).
- pdf.attach: renders each page to PNG via pdftoppm (poppler-utils) at 150 DPI
  and queues the pages as images; 50 MB / 25-page caps. Accepts host path or
  base64 upload.
- Shared helpers (_decode_attach_base64, _sniff_image_ext, _queue_attached_image)
  so the two methods and the existing image.attach don't duplicate logic.

Gateway (hermes_cli/web_server.py):
- GET /api/media: returns a gateway-local image as a base64 data URL so remote
  clients can display it. Auth-gated like every /api route, extension
  allowlist + size cap, AND confined to the gateway's own media roots
  (images/screenshots/cache, resolved symlink-safe) so an authed caller can't
  read image-extension files anywhere on disk.

Desktop (apps/desktop):
- syncImageAttachmentsForSubmit uploads bytes via image.attach_bytes when the
  connection mode is 'remote'; the local fast path is unchanged.
- media.ts gains isRemoteGateway() + gatewayMediaDataUrl(); directive-text and
  markdown-text fetch images over /api/media in remote mode.

Consolidates the competing remote-media PRs (#38876, #40317, #21908, #39437)
into one coherent implementation, taking the strongest parts of each and adding
shared-helper cleanup plus the /api/media root-confinement hardening on top.
The per-profile gateway switching from #38876 is intentionally left out as a
separable feature. TUI file uploads (#40492) remain a separate surface.

Tested: 11 new tui_gateway tests + 5 /api/media endpoint tests + desktop
media.remote unit tests; full tui_gateway + web_server suites green (472
passed); tsc -b clean; E2E verified the full attach→disk→queue and
gateway-path→data-URL display round-trip plus the out-of-root security block.

Co-authored-by: Max Mitcham <maxmitcham@mac.home>
Co-authored-by: Justlrnal4 <Justlrnal4@users.noreply.github.com>
Co-authored-by: Chris Cook <ccook@nvms.com>
Co-authored-by: Thomas Paquette <thomas.paquette@gmail.com>
2026-06-07 10:05:53 -07:00
Teknium a317e54935 chore(release): map Dusk1e and LaPhilosophie for approval fail-closed salvage (#33844, #33866, #30964) 2026-06-07 06:21:37 -07:00
teknium1 fa8fd513ea chore(release): add synapsesx to AUTHOR_MAP for #40495 salvage 2026-06-07 05:01:27 -07:00
manishbyatroy 490c486ff6 fix(simplex): accept display name in SIMPLEX_ALLOWED_USERS
SIMPLEX_ALLOWED_USERS silently denied every contact when operators
listed display names instead of numeric contactIds. The SimpleX UI
never surfaces the numeric id, so display names are what operators
naturally put in the env var. _is_user_authorized only compared
source.user_id (the contactId), so the allowlist never matched.

Expand check_ids to include source.user_name for the simplex platform,
mirroring the existing WhatsApp phone-LID aliasing pattern. Adds doc +
setup-prompt clarification and three regression tests.

Salvaged from PR #40393. Adds manishbyatroy to release.py AUTHOR_MAP.
2026-06-07 04:53:22 -07:00
Teknium 4b9862eb7f chore: map bmoore210 author email for PR #40550 salvage 2026-06-07 02:15:23 -07:00
kshitij 3763355f08
chore(release): map singhsanidhya741@gmail.com to sanidhyasin (#41094)
Adds the AUTHOR_MAP entry for the #40403 salvage (model.default_headers
for custom OpenAI-compatible providers, fixes #40033) so contributor_audit
passes when the salvage PR lands.
2026-06-07 01:55:24 -07:00
teknium1 f9ea4927f2 test(tui): cover _terminal_task_cwd remote-backend branches
Adds regression tests for the SSH cwd fix: local backend keeps
host-validated session cwd; non-local backend uses TERMINAL_CWD (or
terminal.cwd config) verbatim without host isdir() validation; sentinel
values fall back to session cwd.
2026-06-06 18:40:43 -07:00
teknium1 6701c611ba chore(release): map jiangkoumo author email for PR #40540 salvage 2026-06-06 18:36:06 -07:00
Teknium 7db7a9462d fix: align test fixture arg order + add zakame to AUTHOR_MAP
Conflict resolution prefixes --workspace web before --silent (preserving
the Termux npm_workspace_args path); update test_cmd_update fixture to match.
Add zakame@zakame.net -> zakame mapping so CI author check passes.
2026-06-06 18:22:20 -07:00
Brooklyn Nicholson 003110c107 fix(ci): map @TheGardenGallery email + drop unused pytest import
- check-attribution: add chilltulpa@gmail.com -> TheGardenGallery to
  AUTHOR_MAP in scripts/release.py (new external contributor via the
  carried-over commits).
- ty: the dashboard back-compat test imported pytest but never used it,
  tripping unresolved-import. Drop the dead import — tests are plain
  functions driving the parser via subprocess, no pytest API needed.
2026-06-06 12:43:28 -05:00
Teknium fd234bad62
fix(install): detect TLS cert-trust failures during npm install on Windows (#40588)
* fix: respect disabled auto-compaction on context overflow

Port from anomalyco/opencode#30749.

When compression.enabled is false, NO automatic compaction trigger may
fire. The proactive token-threshold paths (preflight + post-response
should_compress gate) already honoured the setting, but the three
provider-overflow recovery paths in the agent loop — long-context-tier
429, 413 payload-too-large, and context-overflow — called
_compress_context() unconditionally, silently compressing and rotating
the session against the user's explicit choice.

Add a single guard at the top of the overflow-recovery dispatch: when
compression is disabled and the error is one of those three overflow
classes, surface a terminal error (compaction_disabled: True) telling the
user to /compress manually, /new, switch to a larger-context model, or
reduce attachments. Manual /compress (force=True) is unaffected — it never
enters this loop.

Tests: new TestOverflowWithCompactionDisabled (413 + 400 overflow don't
compress when disabled; control case still compresses when enabled).
Existing overflow-recovery tests updated to enable compaction explicitly
(they verify the recovery fires); fixture defaults flipped to True to
match production (compression.enabled defaults to True).

* fix(install): detect TLS cert-trust failures during npm install on Windows

Corporate MITM proxies and missing root CAs surface as 'unable to get
local issuer certificate' while npm (most often Electron's install.js
postinstall) downloads over HTTPS. The installer surfaced this as an
opaque 'desktop workspace npm install failed (exit 1)', so users
misread it as a permissions/admin-rights problem (issue #38016).

Add a shared Show-NpmCertHint detector and route all three npm-install
failure paths (agent-browser global install, browser-tools workspace,
desktop workspace) through it. On a cert error it prints actionable
NODE_EXTRA_CA_CERTS / strict-ssl remediation; on any other failure it
stays silent.
2026-06-06 09:00:15 -07:00
Teknium f8a241e105 fix(delegate): flatten content blocks in live overlay tail + AUTHOR_MAP
Follow-up on the cherry-picked content-block fix. _extract_output_tail
(the live subagent overlay) still used crude str(content), which renders
a "[{'type': 'text'...}]" blob and — worse — mislabels a block-wrapped
"Error: ..." result as is_error=False. Route it through the same
_stringify_tool_content helper so error detection and previews work at
both consumer sites.

- delegate_tool.py: _extract_output_tail uses _stringify_tool_content
- tests: add _extract_output_tail content-block test (error detection +
  clean preview)
- release.py: AUTHOR_MAP entry for randomsnowflake (CI gate)
2026-06-05 23:34:00 -07:00
Teknium 3c231eb397
chore: release v0.16.0 (2026.6.5) (#40206)
The Surface Release — native desktop app, browser admin panel,
remote-gateway connect, Simplified Chinese desktop UI, leaner default
skill set, NVIDIA/skills trusted tap, fuzzy model picker, /undo.

874 commits · 542 PRs · 170 contributors · 399 issues closed.
2026-06-05 17:55:43 -07:00
kshitijk4poor 53bba70854 chore: add ohMyJason to AUTHOR_MAP 2026-06-06 01:04:25 +05:30
teknium1 e837856ecd chore(release): map ViewWay author email for AUTHOR_MAP 2026-06-05 09:10:26 -07:00
Teknium 72eb42d9ec
feat(update): stash/restore by default + settable discard for non-interactive updates (reverts #38542, #39568) (#39645)
* Revert "fix(update): require managed marker before destructive clean"

This reverts commit c8e80cd0bf.

* Revert "fix(update): stop stash/restore from clobbering desktop source on managed clones (#38542)"

This reverts commit 8a19884bf3.

* chore(install): keep npm ci desktop-build fix after stash revert

The destructive-clean reverts (#38542/#39568) pulled the desktop
workspace install back to bare `npm install`. The npm ci -> npm install
fallback is orthogonal build-correctness (avoids the Windows
workspace-hoisting flake where install reports up-to-date against a
stale marker while node_modules is empty, breaking tsc -b). Preserve it.

* feat(update): settable stash-or-discard for non-interactive local changes

Adds updates.non_interactive_local_changes (stash | discard, default
stash). Governs ONLY non-interactive updates (desktop/chat app, gateway,
--yes) — interactive terminal updates always stash-and-ask, unchanged.

- config.py: new key under existing updates section; _config_version 26->27.
- main.py: _cmd_update_impl detects non-interactive (gateway/--yes/no-TTY),
  reads the setting; new _discard_stashed_changes() drops the stash
  (stash-and-drop, never reset --hard/clean -fd, so ignored paths survive).
  Post-pull restore site branches on it; the bail-out and up-to-date
  restores always preserve work.
- web_server.py + apps/desktop settings: exposes it as a stash/discard
  select (Advanced section, In-App Update Local Changes).
- docs + tests (discard drops, stash restores, interactive ignores setting,
  missing section defaults to stash).

* fix(install.ps1): stash/restore instead of reset --hard on Windows update

The PR reverted the destructive update path to stash/restore everywhere
except scripts/install.ps1, whose managed-clone update path still ran
`git reset --hard HEAD` before checkout — silently destroying agent-edited
tracked source on Windows (the same #38542 data-loss class the PR fixes).

- Replace `git reset --hard HEAD` with stash-before-checkout +
  restore-after-checkout, mirroring install.sh. Untracked files are
  included so agent-created dirs (e.g. tinker-atropos/) survive.
- Keep `core.autocrlf false` (it prevents the phantom CRLF dirt that made
  the stash necessary; it's also load-bearing for a clean restore).
- Wrap all three checkout modes (Commit/Tag/Branch); Branch case now uses
  `git pull --ff-only` so local commits are never clobbered.
- Only prompt to restore when a real console is attached (UserInteractive
  + non-redirected stdin/stdout + ConsoleHost); the desktop Update button
  and bootstrap have no usable console, so they default to restore and
  never hang on Read-Host.
- On restore conflict or a failed update, the stash is preserved with
  recovery instructions — work is never silently dropped.

Validated on Windows (PowerShell 5.1, git 2.54): AST parse clean;
E2E non-conflicting restore applies+drops cleanly with ignored paths
(node_modules) untouched; conflicting restore preserves the stash.

---------

Co-authored-by: alt-glitch <balyan.sid@gmail.com>
2026-06-05 17:30:10 +05:30
teknium1 397d492b3e chore(release): map harjoth.khara@gmail.com → harjothkhara for #38550 salvage 2026-06-05 02:54:32 -07:00
teknium 2069e78b88 chore: add HeLLGURD to release AUTHOR_MAP for PR #39453 salvage 2026-06-04 19:40:46 -07:00
teknium1 facd011b63 chore(release): map youngstar-eth in AUTHOR_MAP for salvage PR #39134 2026-06-04 19:39:07 -07:00
youngstar-eth 338f0b2234 fix(desktop): recover from corrupt Electron cache in bootstrap install (Windows)
Windows counterpart of #39127: scripts/install.ps1 `Install-Desktop` runs
`npm run pack` once and throws on the opaque ENOENT a corrupt cached Electron
download produces, with no recovery. Add `Clear-ElectronBuildCache` plus a
purge-and-retry-once on pack failure, mirroring the install.sh fix: remove the
cached electron-*.zip (%LOCALAPPDATA%\electron\Cache + ELECTRON_CACHE /
electron_config_cache overrides) and stale *-unpacked output, then retry so
@electron/get re-downloads with its own SHASUM verification.

Refs #37544.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 19:39:07 -07:00
ethernet 80672754a8 fix(docs): update all install instructions everywhere 2026-06-04 21:07:45 -04:00
teknium1 76c7512dbf chore: add Kewe63 gmail to release AUTHOR_MAP 2026-06-04 17:54:59 -07:00
ethernet 36f1cd7dea feat(installer): do shallow clones
no need to get the whole repo history :)
2026-06-04 17:49:16 -07:00
teknium1 0538c5ed19 chore: add dirtyren to AUTHOR_MAP for PR #38177 salvage 2026-06-04 17:42:10 -07:00
Teknium fe4e327bb5 chore: add Kewe63 to release AUTHOR_MAP 2026-06-04 17:40:33 -07:00
Ali Zakaee 454d6cbe52 fix(telegram): finalize sealed overflow chunk so split streamed replies render formatting
The existing-message overflow split path in stream_consumer.run() sealed the
first chunk via _send_or_edit(chunk) (finalize=False) then reset _message_id
to None — so that chunk was never edited again and never received the adapter's
final rich-text pass. On Telegram, MarkdownV2 formatting is applied on the
finalize edit, so early split messages of a long multi-part streamed reply
rendered raw markdown (##, **bold**, code fences) while only the last chunk
rendered correctly.

Fix: seal the overflow chunk with finalize=True so it gets its final
formatting pass before _message_id is cleared.

Salvaged from #32609 (the streaming-format portion only; the PR's send_draft
parse_mode change is already superseded on main, and its media-roots change
conflicts with the current denylist + recency-window delivery model).
2026-06-04 17:11:12 -07:00
teknium1 d1367355d5 chore(release): map jeffrobodie@gmail.com -> jeffrobodie-glitch for salvage 2026-06-04 12:18:38 -04:00
worlldz 081694c111 fix(kanban): isolate board override per concurrent call 2026-06-04 07:39:53 -07:00
teknium1 dd4ba4c2c4 fix(vision): cap pixel dimensions proactively at embed time + declare Pillow
Follow-up to the salvaged #37727. That PR fixed the reactive recovery path
(classifier + post-failure shrinker) but left the PROACTIVE embed-time guard
in vision_tools byte-only — a tall small-byte screenshot (e.g. 1200x12000 at
0.06 MB) still baked into immutable history un-resized, relying on a failed
round-trip to trigger reactive shrink.

- vision_tools: add _image_exceeds_dimension() + _EMBED_MAX_DIMENSION (7900px);
  the embed-time cap now fires on bytes OR pixels and passes max_dimension to
  the resizer, so tall small-byte images are shrunk before they're embedded.
- vision_tools: best-effort lazy-install of Pillow (tool.vision) in the resize
  ImportError fallback so the soft dep self-heals (respects allow_lazy_installs).
- error_classifier: add two more Anthropic dimension-cap wording variants.
- pyproject + lazy_deps: declare Pillow as the [vision] extra / tool.vision
  lazy dep (it was undeclared everywhere; without it ALL resize recovery no-ops).
- tests: cover _image_exceeds_dimension (tall/small/edge/no-Pillow/corrupt).

Co-authored-by: kyssta-exe <kyssta-exe@users.noreply.github.com>
2026-06-04 06:16:45 -07:00
teknium1 7314757876 refactor(feishu): slim meeting-invite parser; add AUTHOR_MAP entry
Collapse the payload-shape normalization helpers into one _as_dict and
drop unused dataclass fields (user_type/user_role, duplicate id, bot) on
the meeting-invite handler. Module 274->212 LOC, behavior unchanged.

Add zhaolei.vc@bytedance.com -> zhaoleibd to release.py AUTHOR_MAP.
2026-06-04 06:15:23 -07:00
teknium1 643181b346 chore: add scubamount to AUTHOR_MAP for salvaged PR #37616 2026-06-04 05:46:13 -07:00
teknium1 c14e6b4edf chore(release): map ashishpatel26 author email for salvage 2026-06-04 05:38:12 -07:00
teknium1 5f199e610b chore(release): add AUTHOR_MAP entry for solaitken 2026-06-04 05:35:43 -07:00
alt-glitch aeec88c77f fix(installer): symlink bundled node/npm into command bin dir for FHS root installs
Root installs on Linux (FHS layout, #15608) put the `hermes` command in
`/usr/local/bin` (on PATH) but symlinked the bundled node/npm/npx into
`~/.local/bin`, which isn't on PATH for a stock root shell. `node`/`npm`
were 'command not found' and `hermes dashboard` failed with 'npm is not
available' because its build-on-demand fallback couldn't find npm.

Fix: `install_node()` now symlinks into `get_command_link_dir()` — the same
helper the `hermes` command link already uses — so node/npm/npx land
wherever the command does (`/usr/local/bin` on FHS root, `~/.local/bin`
otherwise, `$PREFIX/bin` on Termux). Non-root and Termux installs are
unchanged.

Also fixes:
- `scripts/lib/node-bootstrap.sh`: adds `_nb_get_link_dir()` mirroring
  the same root/Termux/user logic for the standalone bootstrap path
  (used by `hermes update`, TUI node bootstrap, etc.)
- `hermes_cli/uninstall.py`: `remove_node_symlinks()` now checks all
  candidate directories (`~/.local/bin`, `/usr/local/bin`, `$PREFIX/bin`)
  so root FHS uninstalls don't leave orphan symlinks

Regression from #15608, which created the FHS path for the command but
left `install_node` pointed at the legacy user-local dir.
2026-06-04 02:31:49 -07:00
Siddharth Balyan f31c950182
refactor(supermemory): session-level ingest + kebab aliases (salvaged from #32487) (#38756)
* refactor(supermemory): session-level conversation ingest + kebab tool aliases

Salvaged from #32487 (by @MaheshtheDev), rebased onto current main.

- sync_turn now buffers cleaned turns; the full session is ingested once
  at session end / switch / shutdown via the conversations endpoint
- ingest_conversation() accepts and forwards functional document metadata
  (type, session_id, message_count, partial)
- register kebab-case tool aliases (supermemory-save/search/forget/profile)
  alongside the snake_case names
- README + docs (EN/zh-Hans) updated for the simplified session model

Source/vendor-attribution removed per project policy (no telemetry):
dropped x-sm-source header, sm_source metadata, and sm_capture_mode tags.
Preserved the post-branch atomic_json_write(mode=0o600) hardening that the
PR's stale base had reverted. Updated provider tests for the new behavior
and added maheshthedev@gmail.com to release.py AUTHOR_MAP.

Co-authored-by: alt-glitch <balyan.sid@gmail.com>

* feat(supermemory): restore x-sm-source for Spaces routing

Reinstates x-sm-source: hermes (SDK default_headers + conversations POST)
and sm_source: hermes document metadata. Per @Dhravya (Supermemory), this
is a functional routing key, not telemetry: it groups Hermes writes into a
dedicated "Hermes" Space in the Supermemory app so users can filter and
bulk-manage memories per source agent.

sm_capture_mode remains dropped (appears analytics-only; Spaces are routed
by sm_source) pending confirmation. Adds README note + a unit test covering
_merge_metadata sm_source stamping and legacy source->type migration.

---------

Co-authored-by: Mahesh Sanikommu <maheshthedev@gmail.com>
2026-06-04 11:50:02 +05:30
Teknium 40420a619b
fix(desktop): attachments on Enter, IME composition, scroll, fetchJson resets (salvage #38502) (#38677)
* fix(desktop): critical fixes — attachments, IME composition, scroll, fetchJson

DC2: Pass attachments to onSubmit() on direct Enter submit and call
clearComposerAttachments().  Previously attachments were silently
dropped — only text was sent while attachment pills remained visible.

DH1: Add 'open' to ThinkingDisclosure ResizeObserver effect deps.
When the disclosure toggles, refs point to new DOM but the observer
wasn't reattached, breaking live-scroll preview after expand/collapse
and leaking detached DOM nodes.

DH3+DH4: Add composition tracking via composingRef (set by
compositionstart/compositionend).  Guards handleEditorInput (skip
preedit state writes), handleEditorKeyDown (prefer composingRef over
unreliable isComposing), and form onSubmit (prevent IME Enter from
triggering submission).  Fixes IME Enter message splitting and preedit
text leaking into app state on CJK input.

DH6: Add res.on('error', reject) to fetchJson response stream.
Without this, a TCP reset mid-transfer left the promise hanging forever,
freezing the desktop UI.

All TypeScript compiles cleanly.

* chore: add copii.list@gmail.com to AUTHOR_MAP (stremtec)

* fix(desktop): prevent scroll snap-back during streaming, atomic config writes

DH2: Defer pinToBottom() in useLayoutEffect to rAF so that browser
scroll/wheel events from the current frame are processed first.
Previously an immediate pinToBottom() could snap the viewport back
to bottom against the user's trackpad scroll-up intent during
streaming — the wheel event hadn't fired yet so stickyBottomRef was
still true.

DH7: Add writeFileAtomic() helper (write to .tmp then rename) and
use it in writeDesktopConnectionConfig, writeDesktopUpdateConfig,
and writeBootstrapMarker.  Prevents partial writes on crash/power
loss that would corrupt JSON config files, requiring manual repair.

* fix(desktop): guard nativeTheme listener from duplicates, invalidate connection config cache

DM9: Guard nativeTheme.on('updated') with a one-shot flag so that
multiple createWindow() calls (e.g. macOS activate after all windows
closed) don't accumulate duplicate listeners on the process-wide
singleton.

DM3: Add mtime-based cache invalidation to readDesktopConnectionConfig.
Previously the cache was populated once and never invalidated — if an
external tool modified connection.json, the desktop ignored the change
until restart.  Now re-reads when the file's mtime differs.

* fix(desktop): widen fetchJson res.on('error') to sibling fetch + sort JSX props

Follow-up to salvaged #38502:
- resourceBufferFromUrl had the same mid-stream-reset hang class as
  fetchJson (req.on('error') present, res.on('error') missing). Add the
  response-stream error handler so a TCP reset during body read rejects
  instead of leaving the promise unsettled.
- Sort the new onComposition* JSX props to satisfy perfectionist/sort-jsx-props
  (was an introduced eslint error in the composer).

---------

Co-authored-by: asill-livestream <copii.list@gmail.com>
2026-06-03 23:38:58 -05:00
Ben Barclay 04d620d91f
fix(docker): run config migrations during container boot (salvage #35508) (#36627)
Salvage of #35508 (@dchenk), rebased onto current main. Resolved the
tests/tools/test_stage2_hook_puid_pgid.py conflict (kept both the
envdir-creation regression test on main and the new config-migration
tests).

Docker image upgrades replace code under $INSTALL_DIR but preserve
$HERMES_HOME on the mounted volume, so the persisted config.yaml never
received the schema migrations that non-Docker `hermes update` runs
(#35406). This adds scripts/docker_config_migrate.py, invoked from
stage2-hook after first-boot seeding and before gateway services start:
it backs up config.yaml + .env, runs migrate_config(interactive=False),
and honors HERMES_SKIP_CONFIG_MIGRATION=1 for manual control.

Also fixes a latent bug in check_config_version(): it called load_config()
which deep-merges DEFAULT_CONFIG, so a legacy config with no raw
_config_version falsely reported as already-current. It now reads the raw
on-disk file so legacy configs are correctly detected for migration.

Differs from #35508 as submitted (Option B cleanup): dropped the
`_config_version` line added to cli-config.yaml.example and removed the
accompanying test_cli_config_example_declares_latest_version change-detector
test. The example is a copy-template and has no business asserting a schema
version; check_config_version() reads the user's real config.yaml, not the
example. This removes a second sync point that drifts on every version bump.

Closes #35508. Fixes #35406.

Co-authored-by: Dmitriy Cherchenko <17372886+dchenk@users.noreply.github.com>
2026-06-04 11:11:27 +10:00
Teknium b0a52d74ac
fix(mcp): resolve ${ENV} in discovery probe so header auth works (#38571)
`hermes mcp add --auth header` built `Authorization: Bearer ${MCP_X_API_KEY}`
and passed it straight to the discovery probe without interpolation, so the
probe sent the literal placeholder and auth-requiring servers (e.g. n8n)
returned 401. Runtime tool loading worked because `_load_mcp_config()`
interpolates, but the four CLI probe call sites (add/test/login/configure)
all used unresolved config.

Resolve `${ENV}` inside `_probe_single_server` via a new
`_resolve_mcp_server_config()` (load_hermes_dotenv + _interpolate_env_vars),
mirroring runtime loading. This covers all four call sites, not just add.

Also strip a leading `Bearer ` from pasted tokens before saving to
`MCP_*_API_KEY`, so a token pasted with the prefix doesn't produce
`Bearer Bearer <jwt>` (also a 401).

Reported with a precise root-cause analysis in #37792.

Co-authored-by: ThyFriendlyFox <116314616+ThyFriendlyFox@users.noreply.github.com>
2026-06-03 17:49:39 -07:00
Teknium 475ecea3d7
fix(install): cap requires-python at <3.14 and pin UV_PYTHON to the venv (#38535)
uv selects the project Python from requires-python and from the UV_PYTHON
env var, both of which override an already-created venv on the next
'uv sync'. With no upper bound on requires-python, an inherited
UV_PYTHON=3.14 (or a fresh distro whose newest interpreter uv auto-picks)
silently recreated the installer's 3.11 venv at 3.14, where Rust-backed
transitives (pydantic-core) have no cp314 wheel and fall back to a maturin
source build that fails. This bit a Windows/WSL user with UV_PYTHON set in
their shell and a fresh WSL-arch box where uv auto-picked 3.14.

Two layers:
- pyproject: requires-python '>=3.11' -> '>=3.11,<3.14' (+ uv lock regen).
  uv now refuses a 3.14 interpreter with a clear error instead of attempting
  the maturin build. Backstop independent of the installer.
- install.sh / install.ps1: pin UV_PYTHON to the venv interpreter after
  creating it (in both the venv step and the deps step, since bootstrap runs
  those stages as separate processes). An inherited UV_PYTHON can no longer
  hijack the sync/pip tiers, so the install just works regardless of shell env.

Verified E2E: hostile UV_PYTHON=3.14 + uv venv --python 3.11 + uv sync now
installs into 3.11 with pydantic-core's 3.11 wheel; without the re-pin the
capped requires-python produces a legible incompatibility error rather than a
cryptic build failure.
2026-06-03 16:45:47 -07:00
Teknium 8a19884bf3
fix(update): stop stash/restore from clobbering desktop source on managed clones (#38542)
The stash/restore cycle in the update path was observed to clobber
freshly-pulled source files (apps/desktop/ deletion -> Vite
'[UNRESOLVED_ENTRY] Cannot resolve entry module index.html'). On a
managed clone the user never edits the source tree, so any 'dirty' state
is pure git artifact (CRLF renormalization, npm lockfile churn, files
left behind when a directory was deleted upstream such as
apps/bootstrap-installer/). Stashing that and re-applying it after a pull
is fragile and unnecessary.

- hermes update (hermes_cli/main.py): on a non-fork (managed) clone,
  discard working-tree dirt via reset --hard HEAD + clean -fd instead of
  stash/apply. Forks keep the stash machinery so intentional edits
  survive. Also pin core.autocrlf=false on Windows so the dirt is never
  created (mirrors install.ps1 #38239).
- install.sh: replace the update-path stash/restore dance with a hard
  reset to origin/<branch>; the installer is a managed-only entry point.
- install.sh + install.ps1 desktop stage: prefer 'npm ci' (wipes and
  reinstalls node_modules from the lockfile) over bare 'npm install',
  which can report 'up to date' against a stale marker while node_modules
  is empty -- leaving tsc unresolved so 'npm run pack' fails.

Tests: managed clone cleans instead of stashing; fork still stashes;
existing stash tests force the stash path explicitly.
2026-06-03 16:40:13 -07:00
Brooklyn Nicholson 1dca7c6207 fix(install): require Node >=20.19/22.12 for the desktop build
The "Build desktop app" install step failed with an opaque "exit code 1"
on machines with an old Node, and nothing in the logs explained it.

Reproduced: on Node 20.5.1, `npm run pack`'s `vite build` crashes with

  You are using Node.js 20.5.1. Vite requires Node.js version 20.19+ or 22.12+.
  SyntaxError: The requested module 'node:util' does not provide an
  export named 'styleText'

Vite 8 (rolldown) imports node:util.styleText, which doesn't exist before
Node 20.12, so the build dies before producing the app. The installer's
check_node / Test-Node accepted ANY pre-existing Node with no version
floor, so a too-old system Node was used for the build instead of the
bundled Node 22.

Add a version floor (^20.19 || >=22.12) to check_node (install.sh) and
Test-Node (install.ps1): a too-old system Node is replaced with the
Hermes-managed Node 22 LTS, and the desktop stage re-resolves Node so the
build always runs on a satisfying version. Declare the same range in
apps/desktop/package.json engines.

Verified: build succeeds on Node 22, fails on 20.5.1 with the error above;
the floor logic matches Vite's range across boundary versions (20.18/20.19,
21.x, 22.11/22.12).
2026-06-03 09:19:04 -05:00
Teknium 214b7e070f
fix(install.ps1): handle dirty worktree on Windows update (#38239)
Git for Windows defaults to core.autocrlf=true, which renormalizes the
repo's LF-only text files to CRLF in the working tree. On a managed,
never-user-edited clone this makes tracked files (.envrc, AGENTS.md,
agent/*.py, workflows) show as locally modified, so the update path's
bare git checkout aborts with 'Your local changes would be overwritten
by checkout' and the desktop bootstrap fails at stage=repository.

The bash installer already autostashes before checkout; the PowerShell
path had no dirty-tree handling at all and never pinned autocrlf.

Fix: (1) git reset --hard HEAD before fetch/checkout in the update path
to discard any pre-existing dirt, and (2) pin core.autocrlf=false on both
the update and fresh-clone paths so the dirt is never created again.
2026-06-03 06:45:48 -07:00
teknium1 b98b645f87 chore: regenerate lockfile + map vladkvlchk for salvaged #36978
- Add @testing-library/dom to apps/desktop devDeps in package-lock.json
  so npm ci validates against the manifest change (contributor left the
  lockfile out of the PR intentionally).
- Removes stale 'peer: true' flags now that dom is an explicit devDep.
- AUTHOR_MAP: prostoandrei9@gmail.com -> vladkvlchk (CI author gate).
2026-06-03 05:50:36 -07:00
kshitij 50ba36dcab
chore: add bbednarski9 to AUTHOR_MAP for #29722 salvage (#38189)
Co-authored-by: kshitijk4poor <kshitijk4poor@users.noreply.github.com>
2026-06-03 05:25:35 -07:00
alt-glitch a1264e9967 fix(matrix): make bang-command resolution robust + fix dead skill-command branch
Follow-up to the salvaged contributor commit:

- Underscore→hyphen tolerance now emits a resolvable token. Previously
  the detect set accepted the hyphenated variant but emit returned the
  raw token, so '!set_home' produced '/set_home' which the dispatcher
  could not resolve. Now emits '/set-home'. Aliases are left as-is — the
  gateway dispatcher canonicalizes them itself.
- Fix dead skill-command branch: skill command keys are stored
  slash-prefixed (e.g. '/arxiv') in get_skill_commands(), but the check
  compared the bare token, so '!arxiv' never normalized. Now compares
  the '/candidate' form, making skill aliases (e.g. !gif-search) work.
- Re-run bang normalization after Matrix reply-fallback stripping so a
  quoted reply whose content is a bang command reaches command parity
  with the slash form.
- Replace silent 'except Exception: pass' with logger.debug(exc_info=True).
- Add AUTHOR_MAP entry for @nepenth.

Tests: +5 (underscore-alias, skill-command branch, quoted-reply bang +
slash parity). 162 Matrix tests pass.
2026-06-03 17:19:27 +05:30
ethernet 43fd63b4b5 fix(windows): rip out unused submodule support in installer & docker & docs
we have no submodules anymore, so #37702 was kinda right, but we can just delete it entirely.
2026-06-03 03:01:37 -07:00
teknium1 1e047677a5 chore: add leonardsellem to AUTHOR_MAP for PR #37405 2026-06-02 18:29:08 -07:00
ethernet 46e513ef51 fix(desktop): configure Linux Electron sandbox helper
Electron's chrome-sandbox helper must be root:root 4755 on Linux or the
sandboxed renderer aborts before the desktop app starts. The existing
installer only searched for macOS .app bundles, so a successful Linux
build was reported as missing.

Changes:
- Add _desktop_linux_sandbox_fixup() to hermes_cli/main.py, called
  before launching a packaged desktop app on Linux.
- Use lstat() + S_ISREG check to reject symlinks — chown/chmod on a
  symlink target would set SUID on an arbitrary path.
- Update install.sh to recognize Linux unpacked artifacts and configure
  chrome-sandbox with proper error handling (the original PR silently
  ignored chown/chmod failures).
- Add regression tests: normal fixup flow, symlink rejection, and
  already-configured skip path.

Closes #37529 (rebased, merge conflicts resolved, copilot review
feedback addressed).
2026-06-02 20:30:13 -04:00
ethernet 4df280d511 refactor(uv): single managed-uv path, delete fts5 installer escalation
Replace the multi-path UV resolution chain (PATH probing, conda guards,
5-location trust ordering, temp-dir fallback installs) with a single
managed uv binary at $HERMES_HOME/bin/uv. Every code path that needs
uv resolves it from that one location; if missing, ensure_uv()
bootstraps it via the official standalone installer.

Key changes:

- New hermes_cli/managed_uv.py: managed_uv_path(), resolve_uv(),
  ensure_uv() (returns (path, freshly_bootstrapped) tuple),
  update_managed_uv(), rebuild_venv(), installer internals.
- hermes_cli/main.py: replace all shutil.which('uv') with ensure_uv(),
  add venv rebuild on first-time managed uv bootstrap, update_managed_uv
  before dep install on all 3 update paths.
- scripts/install.sh: install_uv() always installs to
  $HERMES_HOME/bin/uv; delete ensure_fts5, _python_has_fts5,
  _reinstall_python_with_fts5, _warn_no_fts5 (61 lines).
  Managed uv always installs current Python with FTS5.
- scripts/install.ps1: Install-Uv always installs to
  $HermesHome\bin\uv.exe; Resolve-UvCmd checks managed location first.
- hermes_state.py: simplified FTS5 warning now suggests 'hermes update'
  as the fix instead of blaming install method.
- tests: 15 tests in test_managed_uv.py, autouse _patch_managed_uv
  fixture in test_cmd_update.py.

Closes #37605, Closes #37622
2026-06-02 20:29:54 -04:00
Fearvox 7982560845 fix(release): add fearvox1015@gmail.com -> Fearvox to AUTHOR_MAP
The check-attribution CI job on #37679 failed because the commit
author email nolan@0xvox.com (a local git config mistake on this
machine) is not in scripts/release.py AUTHOR_MAP. The commit
itself is now re-authored to fearvox1015@gmail.com, and this
follow-up adds the entry to AUTHOR_MAP so any future commits
authored from this email also pass the check.
2026-06-02 17:27:44 -07:00