Commit Graph

1792 Commits

Author SHA1 Message Date
Teknium 5342f8613d chore: add AUTHOR_MAP entry for brendandebeasi (PR #29860 salvage) 2026-07-16 04:24:14 -07:00
kshitijk4poor e0240d7bf7 chore: add marcelohildebrand to AUTHOR_MAP for PR #42346 salvage 2026-07-16 15:23:14 +05:30
Teknium 6ee40b65ba chore: add ya-nsh to AUTHOR_MAP 2026-07-16 01:28:13 -07:00
Teknium 094f2b55d3 chore: AUTHOR_MAP entry for 2751738943 (PR #54785 salvage) 2026-07-15 23:51:43 -07:00
ethernet f8ddf4fd86
feat(ci): semantic package-lock.json diff as an upserted PR comment (#65206)
git diff on a lockfile is unreadable: npm reorders entries, rewrites
integrity hashes, and moves packages between nesting levels, so a
one-line package.json bump produces a thousand-line textual diff.

scripts/ci/lockfile_diff.py instead parses the `packages` map out of
both versions of every tracked package-lock.json (via `git show`),
reduces each to {install path: version}, and set-diffs the maps —
reorder/hash churn vanishes, leaving only actual version movement
(added / removed / updated, with nested dedup copies tracked
separately).

The lockfile-diff workflow posts the result as a Markdown table in a
PR comment gated behind a hidden marker: subsequent pushes PATCH the
existing comment instead of stacking new ones, and a push that reverts
all lockfile changes updates the comment to say so. Advisory only —
never fails on findings; fork PRs (read-only token) degrade to a
warning.

Wired through the ci.yml orchestrator with a new npm_lock lane in
classify_changes.py (fails open on .github/ changes per the existing
contract).
2026-07-16 03:18:15 +00:00
ethernet f8b6d381e2
fix(nix): dirty-tree wrapper bug + filtered rebuild scope + overlay alias (#65237)
* fix(nix): fold makeWrapper line continuations into optionalStrings

When rev == null (any dirty-tree build), the empty optionalString
expansion left the previous line's trailing backslash dangling onto a
blank line, ending the makeWrapper command early and running
`--suffix PYTHONPATH ...` as its own shell command (`--suffix: command
not found`, exit 127). Clean trees passed CI; dirty trees with
extraPythonPackages failed — exactly the path the NixOS module
exercises.

The continuation now lives inside each optionalString (" \\\n  --set
..."), so the makeWrapper chain stays intact whether or not the
optional flags expand.

Verified by building with rev = null + extraPythonPackages =
[ pyfiglet ]: wrapper builds, PYTHONPATH suffix lands inside the
makeWrapper call, wrapped `hermes --version` runs, and the collision
check still executes (certifi correctly rejected).

* perf(nix): filter derivation sources to shrink rebuild scope

Every derivation previously saw the whole repo, so any file change
rebuilt everything. Each derivation now gets a filtered src with only
the files it consumes:

- lib.nix: derive npm workspace topology from the root package.json
  `workspaces` globs (single source of truth — a new workspace member
  is picked up with zero nix edits). pythonSrc (cleanSourceWith)
  excludes the JS workspace trees, docs/website, docker/.github,
  tests, nix/, flake.nix/flake.lock, root docs, and skills/ +
  optional-skills/. importNpmLock reads from a fileset-filtered
  npmRoot (root manifests + member package.jsons only).
- mkNpmPassthru takes `dirs` — the workspace dirs the package
  contains — and builds a per-package fileset src from them. web and
  desktop include apps/shared (file: dep). One shared
  `nix run .#update-npm-lockfile` replaces the per-package
  update_*_lockfile bins that only existed inside build sandboxes.
- python.nix: release venv loads the uv2nix workspace from pythonSrc.
  The editable venv keeps an unfiltered ./.. root —
  mkEditablePyprojectOverlay calls lib.path.splitRoot, which rejects
  a cleanSourceWith set, and the editable install reads the live
  checkout anyway.
- hermes-agent.nix: skills ship exclusively via HERMES_BUNDLED_SKILLS
  / HERMES_OPTIONAL_SKILLS (same mechanism as Homebrew packaging;
  setup.py's _data_file_tree returns [] for missing dirs), so
  SKILL.md edits no longer rebuild the venv. optional-mcps stays in
  the wheel — pyproject.toml lists its manifests as explicit
  data-files. Bundled assets are symlinked instead of copied, making
  the wrapper drv near-instant when only an input changed.
  __pycache__ filtered from bundled skills.
- checks.nix: find -L through the new symlinks; assert
  optional-skills presence + HERMES_OPTIONAL_SKILLS in the wrapper.
- run_tests.sh: fall back to $HERMES_PYTHON when no local venv
  exists, guarded by an `import pytest` probe (HERMES_PYTHON from a
  wrapped hermes binary points at the release venv, which has no
  pytest — without the guard every test file dies with "No module
  named pytest" while the runner exits 0).

Verified: nix flake check exit 0; built .#default .#tui .#web
.#desktop; SKILL.md and flake.nix edits leave the venv drvPath
unchanged; .py edits leave the tui drvPath unchanged; .tsx edits
leave the venv drvPath unchanged (and do change the tui drv);
scripts/run_tests.sh runs 299 tests green through both the venv and
HERMES_PYTHON paths, and rejects a pytest-less HERMES_PYTHON.

* refactor(nix): overlay aliases the flake's own package instead of re-instantiating

The overlay previously re-called callPackage against the consumer's
nixpkgs (final), so pkgs.hermes-agent could be a different derivation
than nix build .#default and the NixOS module's default — an untested
build matrix against arbitrary consumer nixpkgs versions, for a
package whose Python side is uv2nix-locked anyway.

Now the overlay is a pure alias for the flake's own locked package:
one callPackage site (packages.nix), everything else references it.
.override { ... } still works — callPackage's makeOverridable travels
with the derivation.

Verified: direct drvPath == overlaid drvPath; .override produces a
distinct drv.

* fix(nix): dedupe extraPlugins assertions, replace MESSAGING_CWD with terminal.cwd

- Delete the duplicated extraPlugins duplicate-name assertions block
  (same assertion declared twice back to back).
- Stop setting the deprecated MESSAGING_CWD env var, which made the
  module trip hermes' own startup deprecation warning. The working
  directory is now injected as terminal.cwd into the generated
  config.yaml; cfg.settings wins via recursiveUpdate, and container
  mode maps to the in-container mount path.
2026-07-15 21:45:35 -04:00
Ben Barclay 3f2a389c7e
fix(auth): apply newer hosted bootstrap session (#64612)
* fix(auth): apply newer hosted bootstrap session

* fix(auth): validate rebootstrap replacement seeds
2026-07-16 08:52:47 +10:00
ethernet ef7aabd3d1 ci: add ci-reviewed label gate for CI-sensitive files 2026-07-16 01:42:02 +05:30
kshitij 2b6897f982
fix(computer-use): target Linux app windows reliably (#63725)
* fix(computer-use): target Linux app windows reliably

Resolve app filters through the canonical cua-driver MCP app metadata and join running app PIDs back to windows. Preserve an exact selected window across capture_after, support direct capture by pid/window_id, and send the active window ID for coordinate pointer actions on Linux.

Co-authored-by: annguyenNous <annguyenNous@users.noreply.github.com>
Co-authored-by: grimmjoww578 <willies578@gmail.com>
Co-authored-by: ai-ag2026 <261867348+ai-ag2026@users.noreply.github.com>

* fix(computer-use): address review on PR #63725

Address three review comments from @f-trycua:

1. type_text, press_key, and hotkey now carry _active_window_id and
   fail closed when it is missing. Previously they sent only the PID,
   so CUA Driver fell back to the first window for that PID — input
   could reach the wrong window in multi-window apps.

2. Coordinate scroll x/y are now capability-gated behind
   input.scroll.coordinates. CUA Driver 0.7.1 Linux schema rejects
   x/y on scroll; omitting them when the driver doesn't advertise
   support avoids the schema rejection while still routing via
   window_id.

3. Windows are sorted by z_index descending (higher = front, per CUA
   Driver semantics) instead of ascending. Null z_index (Wayland) is
   coerced to 0 in _ingest_windows so it doesn't crash the sort and
   sorts to the back instead of being selected as the capture target.

---------

Co-authored-by: LeonSGP43 <cine.dreamer.one@gmail.com>
Co-authored-by: annguyenNous <annguyenNous@users.noreply.github.com>
Co-authored-by: grimmjoww578 <willies578@gmail.com>
Co-authored-by: ai-ag2026 <261867348+ai-ag2026@users.noreply.github.com>
2026-07-16 00:34:20 +05:30
Teknium 6020b9f4fe chore: AUTHOR_MAP entry for JiaDe-Wu (PR #34742 salvage) 2026-07-15 09:59:38 -07:00
Teknium f8630a1456 chore: add Burgunthy to AUTHOR_MAP (PR #20096 salvage) 2026-07-15 09:50:05 -07:00
Teknium 306e2d2318 chore: AUTHOR_MAP entry for Epoxidex (PR #29820 salvage) 2026-07-15 06:38:28 -07:00
Teknium a7ef17da7a chore(release): map dorokuma in AUTHOR_MAP 2026-07-15 06:25:10 -07:00
Teknium 2fc0e3d1aa fix(codex): guard the continuation nudge against role-alternation violations
Follow-up to the salvaged #63690: when the interim assistant message is
too empty to append (no content and no reasoning of any kind), the last
message in history is still the prior user/tool turn — appending the
user-role nudge there would create a user→user or tool→user sequence
that strict providers reject. Only append the nudge when the last
message is an assistant turn. Also add IpastorSan to AUTHOR_MAP.
2026-07-15 00:13:24 -07:00
Teknium b34e565957 feat(models): catalog-labeled silent default — GLM-5.2 marked "default": true in the model catalog
The remote model catalog (website/static/api/model-catalog.json) now labels
exactly one entry per provider block with "default": true — z-ai/glm-5.2 for
both OpenRouter and Nous Portal. That labeled entry is the model Hermes
silently lands on when the user never picked one, and it can be rotated by
editing the manifest alone: no release needed.

- model_catalog.py: get_default_model_from_cache() reads the label from the
  in-process/disk cache only — never triggers a network fetch, so hot
  resolution paths (agent build, gateway session setup) stay network-free.
- models.py: get_preferred_silent_default_model() resolves catalog label
  first, PREFERRED_SILENT_DEFAULT_MODEL constant second (offline/fresh
  install). _PROVIDER_SILENT_DEFAULT_OVERRIDES dict replaced by
  _SILENT_DEFAULT_PROVIDERS routing through the shared resolver.
  fetch_openrouter_models() preserves the "default" badge through live
  /v1/models refreshes so the picker shows it.
- scripts/build_model_catalog.py: generator emits the default label so
  regeneration can't drop it.
- website/docs/reference/model-catalog.md: schema documents the new field.
- Salvaged from PR #61141 (@HumphreySun98): bare-provider /model switches
  (/model nous) route through the cost-safe default instead of curated
  entry [0].
- tests: catalog-label precedence, constant fallback, stale-label fallback,
  cache-only (no network) guarantee, and a shipped-manifest contract test
  pinning the labeled entry to PREFERRED_SILENT_DEFAULT_MODEL.

E2E (temp HERMES_HOME): fresh-install constant fallback, shipped-manifest
label read, release-free rotation (relabeled cache -> new default across
models.py, tui_gateway, and gateway empty-model paths) all verified.
2026-07-15 00:10:31 -07:00
Teknium d6c14a952f chore(release): map liuwei666888 in AUTHOR_MAP 2026-07-15 00:10:22 -07:00
kshitijk4poor d2c81eb681 chore(release): map pixel4039 in AUTHOR_MAP (PR #64420 salvage) 2026-07-15 10:58:36 +05:30
Teknium e357b69a61 chore(release): map KCAYAAI in AUTHOR_MAP 2026-07-14 21:31:32 -07:00
Teknium 7df595c58b chore: AUTHOR_MAP entry for webtecnica (PR #63360 salvage) 2026-07-14 21:31:04 -07:00
Teknium 5d410355ac chore(release): map justinschille in AUTHOR_MAP 2026-07-14 21:08:22 -07:00
kshitijk4poor 4554fe128a fix(slack): complete agent view workspace routing 2026-07-14 13:58:36 -07:00
kshitijk4poor 0d01831919 chore: add changhyun.min@gmail.com to AUTHOR_MAP (minchang, PR #42231) 2026-07-15 00:09:24 +05:30
Teknium 46e87b14fd chore(release): map unsupportedpastels in AUTHOR_MAP 2026-07-14 07:02:05 -07:00
Teknium 72ac0d6af0 chore: add neo-claw-bot to AUTHOR_MAP (PR #58465 salvage) 2026-07-14 06:49:36 -07:00
kshitijk4poor 444b5e96fa chore(release): map arnispiekus in AUTHOR_MAP
For PR #63581 salvage (telegram: require getUpdates progress before
polling is healthy).
2026-07-14 17:46:21 +05:30
kshitijk4poor c5aaf76468 chore(release): map @Roseyco-management in AUTHOR_MAP
For PR #63581 salvage (telegram: require getUpdates progress before
polling is healthy). SilentKnight87 uses a noreply GitHub email which
auto-skips.
2026-07-14 17:32:41 +05:30
Vishal Dharmadhikari b8eb89f5c9 feat(gemini): improve request context for support and compatibility
Include the Hermes client name and version with Gemini inference, model and tier checks, and TTS requests. Add focused coverage for the request headers and keep the Gemini-specific context scoped to Google Gemini endpoints.
2026-07-13 22:26:19 -07:00
kshitijk4poor 2ccfdb2db4 fix(agent): exempt parseable vLLM/LM Studio output-cap errors from compression-disabled guard
Salvage of #63862. is_output_cap_error() returns False for vLLM/LM Studio
error messages that contain 'prompt contains ... input tokens' (treated as
input-overflow signal). But parse_available_output_tokens_from_error() CAN
extract a valid available_tokens from those same messages. The
compression-disabled guard only checked is_output_cap_error(), so vLLM/LM
Studio users with compression off still got a terminal failure instead of
the max-tokens retry.

Fix: also exempt when parse_available_output_tokens_from_error() returns a
value — that function determines whether the retry path can actually handle
the error, so it's the right predicate for the exemption.

Added test: verify vLLM-format error with compression_disabled=False still
triggers the max-tokens retry path.

Co-authored-by: dmabry <dmabry@users.noreply.github.com>
2026-07-14 03:58:54 +05:30
ethernet 29b8cacfab fix(ci): add missing Δ Wait column to skipped job rows 2026-07-13 17:46:11 -04:00
ethernet e117478eb3 fix(ci): align baseline gantt bars to current job start
Baseline bars were positioned at their absolute timeline offset, which
included the baseline run's own wait time — making duration comparisons
hard since the bars were visually offset. Now each baseline bar starts
at the same left position as its corresponding current job, so the two
bars directly overlap for at-a-glance duration comparison.

Also removed the now-unused bl_t0 / bl_max / bl_jobs_timed variables
that tracked the baseline timeline position.
2026-07-13 17:46:11 -04:00
ethernet f0b7cf3836 feat(ci): show per-job wait times in timing report
Add wait time computation: for each job, wait_s = started_at - max(completed_at
of all jobs that finished before it started). This is a timestamp heuristic
(no workflow YAML dependency parse needed) that's accurate for pipeline-shaped
CI where the critical path is linear at each stage.

Shows up in:
- Job table: new Wait + Δ Wait columns
- Gantt chart: hatched bar segment before the run bar
- Step details: '(wait Xs)' annotation in the summary line
- Markdown summary: Total wait row with delta vs baseline
- Stats: total_wait / bl_total_wait in compute_stats

Also completes the skipped-jobs UI:
- Skipped stat card in stats cards
- Skipped row in markdown summary table

Backward compatible: old cached baselines without wait_s annotate on load.
2026-07-13 17:46:11 -04:00
ethernet 7beca22bc0 fix(ci): exclude skipped jobs from timing deltas and stats
Skipped jobs (conclusion == 'skipped') have null/zero-duration timestamps
that polluted every downstream computation: they counted as 'unchanged'
(0 vs 0) in faster/slower tallies, showed meaningless '0.0s (0%)' deltas
in the job table, rendered phantom gantt bars, and inflated wall/compute
totals.

Add is_skipped() helper and apply it consistently:
- compute_stats: exclude from wall/compute + faster/slower/unchanged;
  add 'skipped' and 'bl_skipped' counts
- _gantt_bars: filter from current bars, baseline bars, and axis calc
- _job_table: show 'skipped' label instead of durations/deltas
- _step_details: skip entirely (no meaningful step data)
- _regressions: exclude from both current and baseline sides
2026-07-13 17:46:11 -04:00
kshitijk4poor 2fc3f9c1ff fix(deepinfra): harden multimodal provider routing
Prevent credential forwarding across catalog redirects, retain explicit opt-in semantics for paid media backends, fail closed on invalid provider configuration, avoid mixed-catalog and output-limit assumptions, and reserve native STT provider names.
2026-07-14 02:59:39 +05:30
ethernet 7fe1cb384e feat(ci): python test speedups 2026-07-13 15:29:20 -04:00
kshitijk4poor 2627933f33 fix(agent): distinguish missing from broken compression locks 2026-07-14 00:14:55 +05:30
kshitijk4poor 0512f06a6a fix(auth): centralize pool auth normalization
Normalize Anthropic setup-token metadata for every PooledCredential construction path, persist corrected manual entries, heal legacy rows on load without copying global fallback credentials into profiles, and map the contributor email for release attribution.
2026-07-13 23:20:48 +05:30
teknium1 d2b2be0f01 chore(release): map delegation contributor 2026-07-13 07:28:21 -07:00
Teknium d921005e25 chore(release): map @kavioavio in AUTHOR_MAP 2026-07-13 04:31:55 -07:00
Teknium f813c7ddad chore(release): map @Tortugasaur desktop commits 2026-07-13 03:00:43 -07:00
Teknium 0709714a6c chore(release): map @yinkev in AUTHOR_MAP 2026-07-12 23:09:41 -07:00
Teknium acb3bde097 chore(release): map @jakelongvu-bot in AUTHOR_MAP 2026-07-12 22:43:57 -07:00
Jeffrey Quesnelle 6d3009454d
Merge branch 'main' into bb/salvage-43809-wsl-bridge 2026-07-12 22:46:49 -04:00
Teknium 7c19eb80a8 docs(dashboard): align approval mode guidance 2026-07-12 17:42:42 -07:00
teknium1 68107ae9d0 chore(release): map @ansel-f in AUTHOR_MAP 2026-07-12 04:32:52 -07:00
Brooklyn Nicholson 650d04d879 chore(attribution): map VrtxOmega@pm.me -> VrtxOmega (#43809 salvage) 2026-07-12 05:32:54 -04:00
Brooklyn Nicholson 33ab65a14f chore(attribution): map palmer@dugoutfantasy.com -> professorpalmer (#48591 salvage) 2026-07-12 05:08:13 -04:00
Brooklyn Nicholson e0a650fa7d refactor(desktop): text-only workspace status menu + attribution
Align the workspace status-bar dropdown with the rest of the status bar: drop
the per-item icons (they mixed lucide size-4 with a Codicon 1rem glyph and were
the only status-bar menu carrying item icons), leaving text-only items on the
shared DropdownMenuItem primitive with default typography. The status-bar
trigger keeps its FolderOpen glyph, consistent with sibling items.

Also map true@supersynergy.de → Supersynergy in AUTHOR_MAP.
2026-07-12 04:56:24 -04:00
Brooklyn Nicholson f63535a347 chore(attribution): map esthon@gmail.com → esthonjr (#61950 salvage) 2026-07-12 04:45:56 -04:00
kshitijk4poor 37a942650f fix(gateway): scope context refs to runtime profile 2026-07-11 23:46:13 +05:30
kshitijk4poor a46bb90d6b chore: map PR #62125 contributor 2026-07-11 22:59:49 +05:30
teknium1 c552984534 chore: map usage attribution contributors 2026-07-11 05:59:42 -07:00
teknium1 31152ae108 fix(providers): align Fireworks integration with project policy 2026-07-11 05:43:35 -07:00
ethernet 5e849942c3 feat(dev): add isolated sandbox script for local dev
scripts/desktop-sandbox.sh runs a Hermes desktop instance in an isolated
sandbox — separate HERMES_HOME, separate Electron userData, and a
distinct
app name (HERMES_DESKTOP_APP_NAME) so it doesn't compete with the main
desktop instance's single-instance lock.

Two modes:
- Ephemeral (default): temp dir, cleaned up on exit
- --persistent: stored under .hermes-sandbox/ in the worktree git root,
  survives restarts for repeat testing

In the Nix devShell the script is available as 'sandbox'.

Also makes APP_NAME overridable via HERMES_DESKTOP_APP_NAME in main.ts —
app.setName() runs before requestSingleInstanceLock(), so the overridden
name changes the lock key. collectRelaunchEnv already preserves
HERMES_DESKTOP_* vars through self-update relaunches; test updated to
cover the new env var.
2026-07-10 18:51:49 -04:00
Teknium 35d777df07 chore: map WilsonKinyua release attribution 2026-07-10 07:47:10 -07:00
teknium1 651e632b6d fix(feishu): ship Channel signaling SDK support 2026-07-09 20:31:49 -07:00
teknium1 c9d5491205 chore: AUTHOR_MAP entries for HumphreySun98 + 17324393074 (PR #61142/#53196 salvage) 2026-07-09 20:28:44 -07:00
teknium1 c49d51bf72 chore: AUTHOR_MAP entry for kohoj (PR #61667 salvage) 2026-07-09 19:54:02 -07:00
teknium1 540f90190f chore: map reconnect contract contributor 2026-07-09 19:09:38 -07:00
teknium1 9c40fc2f2c chore: map QQBot fix contributor 2026-07-09 19:09:38 -07:00
Teknium bd16395255 chore: add AlexFucuson9 to AUTHOR_MAP (PR #61347 salvage) 2026-07-09 18:23:10 -07:00
teknium1 a801046669 fix(memory): resolve() the shared-connection registry key; symlink test
Follow-ups for salvaged PR #43819: the registry key was
str(Path(db_path).expanduser()) — a symlinked or relative path to the
same DB file got its own connection, silently reintroducing the exact
multi-writer contention the registry prevents. Key on Path.resolve()
(OSError-tolerant fallback). Adds a symlink regression test and the
AUTHOR_MAP entry for adambiggs.
2026-07-09 18:17:40 -07:00
teknium1 79f1274802 chore: AUTHOR_MAP entry for AlexFucuson9 (PR #61209 salvage)
His noreply email has no numeric-id+ prefix, so the attribution CI's
auto-resolve pattern doesn't match it.
2026-07-09 18:04:00 -07:00
teknium1 10c0d9b2a7 fix(cron): contain any per-job exception in the due scan; harden as a class
Structural completion of the malformed-job freeze fixes (#61382 id-less,
#61525 non-dict schedule, #61581 bad next_run_at): wrap the per-job body
of _get_due_jobs_locked in try/except so any FUTURE malformed-field
variant degrades to skipping that one job for the tick instead of
aborting the scan before save_jobs() and freezing the whole profile's
scheduler.

Also: restore test_repeated_concurrent_runs_accumulate_completed_count
to TestMarkJobRunConcurrency (accidentally re-parented by the #61581
diff), add a containment regression test, and AUTHOR_MAP for hydracoco7.

E2E: one jobs.json carrying all five malformed shapes (drifted job_id,
missing id, null schedule, garbage next_run_at, non-string last_run_at)
plus a healthy sibling — single tick contains all five, sibling fires,
repairs persist, second tick stable. 670 cron tests green.
2026-07-09 17:31:47 -07:00
teknium1 d2e64fcb89 fix(cli): widen --yolo env guarantee to the _prepare_agent_startup chokepoint + AUTHOR_MAP
The salvaged fix sets HERMES_YOLO_MODE in main()'s dispatch path before
_prepare_agent_startup(); this follow-up also sets it inside
_prepare_agent_startup() itself so every launcher that triggers plugin/tool
discovery (incl. the Termux fast-CLI path) gets the same ordering guarantee
before tools.approval freezes _YOLO_MODE_FROZEN (#60328).
2026-07-09 16:58:37 -07:00
teknium1 daedf4f627 chore: AUTHOR_MAP entry for embwl0x (PR #60810 salvage) 2026-07-09 06:27:04 -07:00
Kshitij Kapoor f556edc10d fix(model_metadata): address Phase-2 review findings on probe caches
Structured review (2a/2b/2c) findings, all fixed:

- MAJOR: detect_local_server_type memo was process-lifetime with no
  invalidation, permanently pinning a URL's server type. Now a bounded
  1h TTL ((type, monotonic) tuples) so a backend swap on the same port
  is re-detected. Test covers ollama->lm-studio swap after expiry.

- MAJOR: legacy disk-row compat was one-way. get_cached_context_length
  and _invalidate_cached_context_length now consult the same key-shape
  set {canonical, literal, canonical+slash} in both directions, so an
  old slashed row is found (and cleared) when the runtime passes the
  normalized URL. Tests pin both migration directions.

- MINOR: _localhost_to_ipv4 did whole-string replacement, which could
  corrupt a proxy URL embedding http://localhost in its query. Now a
  scheme-anchored host-only regex; localhost.example.com and embedded
  substrings pass through. Tests added.

- MINOR: _invalidate_cached_context_length now also drops the
  in-memory TTL probe rows for the pair, so a resolution inside the
  TTL window can't re-persist the value just declared stale.

- Test gaps closed: detect-type cache hit + TTL-expiry re-detection,
  ollama-show TTL expiry re-probe, reverse legacy-row lookups.

- Attribution gate: added zhchl@hermes-agent.local -> 8294 (PR #50572
  author) to AUTHOR_MAP; the strict CI grep needs bare non-plus emails
  literal in release.py.

Gates: ruff clean; targeted suites 202 passed / 0 failed; full
tests/agent 5426 passed with 17 failures identical on clean
upstream/main (pre-existing env-dependent anthropic/bedrock/credpool
tests); mypy delta vs base: 0 new errors; live smoke 6/6 PASS.
2026-07-09 14:08:44 +05:30
uzaylisak c889941916 fix(model_metadata): cache detect_local_server_type result for process lifetime
Every 5 minutes fetch_endpoint_model_metadata() re-runs the full server-type
waterfall (LM Studio -> Ollama -> llama.cpp -> vLLM), spraying 404s at
endpoints the server never exposes (e.g. /api/v1/models and /api/tags on a
vllm backend).

Add _endpoint_probe_path_cache (base_url -> server type) so the first
successful probe's result is reused for the lifetime of the process.
Subsequent refreshes skip straight to the known-good path.

Fixes #29971.

(cherry picked from commit f3d7a8960a)
2026-07-09 14:08:44 +05:30
kshitijk4poor a4ba8c9640 chore: map poowis2011@hotmail.com → Umi4Life for PR #47377 salvage 2026-07-09 13:40:19 +05:30
ethernet 39d09453f9 feat(desktop): ts-ify everything 2026-07-08 16:24:16 -07:00
kshitijk4poor 449706cb52 chore: add dexhunter to AUTHOR_MAP (PR #60339 salvage) 2026-07-09 02:41:24 +05:30
kshitijk4poor 1792a3aa72 chore: add gauravsaxena1997 to AUTHOR_MAP (PR #59868 salvage) 2026-07-09 02:01:56 +05:30
kshitijk4poor 4f220fc88b fix: follow-up for salvaged #60347/#43653/#47437
- derive the compact_rows projection from SCHEMA_SQL (parse once, cache)
  instead of a hardcoded column list: the original #47437 list was cut
  against a June schema and silently dropped session_key/chat_id/chat_type/
  thread_id/display_name/origin_json/expiry_finalized/git_branch/
  git_repo_root/compression_failure_* — including desktop sidebar fields.
  Schema-derived means declaratively reconciled new columns are included
  automatically; only system_prompt is excluded.
- guard test pinning the schema<->projection contract (mutation-verified:
  dropping a column from the projection fails it)
- wire compact_rows=(not full) into /api/sessions and /api/profiles/sessions
  so the SQL projection pairs with the API-level field strip (?full=1 still
  returns complete rows end-to-end)
- pass compact_rows at the remaining hot list callers: /api/status active
  count, _session_latest_descendant fallback, /api/sessions/stats by-source
- thread compact_rows through the compression-tip projection
  (_get_session_rich_row) so projected tips can't reintroduce the blob
- add pagination tests for get_messages (#60347 shipped none): paging order,
  offset-past-end, active-flag interaction; add tip-projection compact test
- AUTHOR_MAP entries for mahdiwafy + CodeForgeNet (plain emails)
2026-07-09 01:36:46 +05:30
kshitijk4poor 206c042392 chore: AUTHOR_MAP entries for salvaged contributors (#53511/#53966/#39140) 2026-07-09 01:19:07 +05:30
teknium1 4d611ba0c3 chore: AUTHOR_MAP entry for nullptr0807 (PR #60956 salvage) 2026-07-08 12:35:50 -07:00
teknium1 ae5e39005b fix(gateway): run webhook route scripts off the event loop + AUTHOR_MAP entry
- run_route_script shells out with subprocess.run (up to 30s timeout); wrap
  the call in asyncio.to_thread so a slow script can't stall every other
  webhook and gateway task on the loop.
- scripts/release.py: map grace@weeb.onl -> evelynburger for the salvaged
  contributor commit.
2026-07-08 08:10:55 -07:00
teknium1 5413c42f2c chore: add SiteupAgencia to AUTHOR_MAP for #57435 salvage 2026-07-08 06:15:43 -07:00
kshitijk4poor c75e1d1b87 chore: add veradim to AUTHOR_MAP for PR #41201 salvage 2026-07-08 17:33:40 +05:30
teknium1 a208b7eeb4 chore: add AUTHOR_MAP entry for neoguyverx (PR #60526 salvage) 2026-07-07 22:14:33 -07:00
Teknium c30c9753b6
fix(whatsapp): unpin Baileys from git commit, use published 7.0.0-rc13 (#60643)
The April 2026 pin to WhiskeySockets/Baileys#01047deb existed only to
pick up the abprops bad-request fix (Baileys PR #2473) before it was
released. That fix shipped in v7.0.0-rc11 (May 2026); our pinned commit
is now 48 commits behind rc13.

The git pin forced npm to clone the repo and compile Baileys from
TypeScript source on every fresh install (~3 min), which blew past the
dashboard pairing flow's timeout. Registry install takes ~3s.

Validation: all 9 bridge.js imports present in rc13, bridge.native.test.mjs
passes (13/13), live bridge boot renders pairing QR against real WA servers.
2026-07-07 19:49:26 -07:00
Shannon Sands 4f620a0bbc Add WhatsApp dashboard pairing flow 2026-07-07 17:45:51 -07:00
teknium1 bae3954f4f chore(release): map rainbowgore + thestudionorth in AUTHOR_MAP for MCP leak salvages 2026-07-07 15:16:00 -07:00
teknium1 d5a5ea8640 chore: add doncazper to AUTHOR_MAP 2026-07-07 15:14:30 -07:00
Ronald Reis a0b14a3126 chore: map Ronald contributor email 2026-07-07 15:12:54 -07:00
teknium1 94b4ac118a chore: add alex107ivanov to AUTHOR_MAP 2026-07-07 13:46:51 -07:00
teknium1 f76899facf feat(sessions): wire html + prompt-only formats into 'sessions export'
Salvage follow-up integrating PR #30481 (@simplast) and PR #57683
(@catbearlove1-lang) into the unified export surface:

- --format html: standalone self-contained HTML transcript (single
  session or multi-session with sidebar), works with all shared filters
  and --redact; requires a file output path.
- --only user-prompts: prompt-only export (jsonl records or md sections)
  via the shared session_export renderer; the separate export-prompts
  subcommand from the original PR is subsumed by this flag.
- AUTHOR_MAP entries for both contributors; docs EN + zh-Hans.
2026-07-07 13:29:58 -07:00
teknium1 f3c27e30eb refactor(sessions): fold Markdown/QMD export into 'sessions export --format'
Replaces the parallel export-md subcommand from the salvaged commit with
a --format jsonl|md|qmd flag on the existing export subcommand, so all
session export formats share one surface. Adds AUTHOR_MAP entry.

Salvage follow-up for PR #59542 by @web3blind.
2026-07-07 12:36:41 -07:00
HexLab98 2bc6e1a74b fix(installer): put node.exe on PATH for Windows npm lifecycle scripts
npm postinstall hooks spawn cmd.exe child processes that could not resolve
`node` even when the installer found npm — causing desktop workspace npm
install to fail with exit 1 (#48130).
2026-07-07 21:17:34 +07:00
teknium1 3c63ed3a3c chore: add vampyren to AUTHOR_MAP (PR #59830 salvage) 2026-07-07 05:54:29 -07:00
teknium1 685f527d6b chore: add andrewhomeyer to AUTHOR_MAP (co-author on snapshot perms salvage) 2026-07-07 05:22:42 -07:00
teknium1 1deeaf71ab fix(discord): truncate thread titles by UTF-16 units + AUTHOR_MAP
Discord thread names share the same UTF-16 component budget as select
labels and buttons — route the sanitizers in gateway/run.py and the
adapter's rename_thread through utf16_len/_prefix_within_utf16_limit
instead of code-point slices. Adds rungmc357 to AUTHOR_MAP.
2026-07-07 05:11:59 -07:00
teknium1 b2c66681c4 chore: add flo1t to AUTHOR_MAP 2026-07-07 04:09:09 -07:00
hmirin d1c8c03416 feat(agent): add Codex-native compaction paths 2026-07-07 02:39:54 -07:00
teknium1 179ca25a38 chore: add williamumu to AUTHOR_MAP for PR #31041 salvage 2026-07-07 02:18:17 -07:00
Teknium 043e71f1f4
fix(gateway): use process-level HERMES_HOME for identity files (#56993 salvage) (#59341)
* fix(gateway): use process-level HERMES_HOME for identity files

Gateway identity files (PID, lock, runtime status, takeover/stop markers)
were written via get_hermes_home() which honours the _HERMES_HOME_OVERRIDE
contextvar used for per-session profile dispatch.  When a profile-context
task happened to be active at write time, files landed in the wrong profile
directory.

Add _get_process_hermes_home() that skips the contextvar and uses only the
HERMES_HOME env var or platform default, and route all gateway identity file
paths through it.

Fixes #56986

* chore(release): map liuhao1024 author email for PR #56993 salvage

---------

Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
Co-authored-by: Ben <ben@nousresearch.com>
2026-07-07 09:05:21 +00:00
Ben Barclay 4b9d9b205b
fix(dashboard): use loopback host for in-container WebSocket client (#58993) [salvage #59682] (#60092)
* fix(dashboard): use loopback host for in-container WebSocket client (#58993)

Fixes #58993 - the in-container Dashboard's WebSocket client was dialing
the bind host (0.0.0.0) instead of 127.0.0.1, hijacking the host browser
when the container port was exposed.

* `hermes_cli/web_server.py::resolve_dashboard_ws_url()` now substitutes
  127.0.0.1 for any 0.0.0.0 bind host discovered via the existing
  `find_unused_port` / `get_listen_address` path. LAN IPs and explicit
  `DASHBOARD_WS_HOST` overrides pass through unchanged.
* Existing tests preserved (no regression on the explicit-bind case).

Tests in `tests/dashboard/test_ws_client_host.py` cover:
- Bind host 0.0.0.0 → ws URL uses 127.0.0.1
- Bind host 127.0.0.1 → ws URL uses 127.0.0.1 (no regression)
- Bind host 192.168.1.5 → ws URL preserves the LAN IP
- DASHBOARD_WS_HOST env override wins over auto-detection

AI-assisted fix by https://github.com/SquabbyZ/peaks-loop

(cherry picked from commit 5501dd38d6)

* chore(release): map SquabbyZ email for AUTHOR_MAP attribution (#59682)

---------

Co-authored-by: SquabbyZ <601709253@qq.com>
2026-07-07 18:33:28 +10:00
Teknium 76979a0869
fix(auth): per-profile Anthropic OAuth file + complete port-binding platform set (#57563 salvage) (#59339)
* fix(auth): resolve Anthropic OAuth file per-profile + close port-binding platform gaps

Two focused pieces salvaged from PR #57563:

1. _HERMES_OAUTH_FILE was computed at module import time — frozen before
   HERMES_HOME/profile overrides, so multiplexed profile turns read and
   wrote the DEFAULT profile's .anthropic_oauth.json (OAuth path hijack).
   Replaced with a lazy _get_hermes_oauth_file(); all web_server.py call
   sites updated.

2. _PORT_BINDING_PLATFORM_VALUES was missing whatsapp_cloud and line —
   both bind aiohttp TCP listeners, so a secondary multiplex profile
   enabling them would collide with the primary's listener instead of
   failing fast at startup.

Original work by @austinlaw076. The rest of #57563 was redundant on
main (adapter routing sweep superseded by #56854's salvage; cron secret
scope landed in fdab380a1; nested-config fallback in from_dict).

* chore(release): map austinlaw076 author email for PR #57563 salvage

* test(hermes_cli): patch _get_hermes_oauth_file instead of removed _HERMES_OAUTH_FILE constant

---------

Co-authored-by: Austin <austin@openvm067.space>
Co-authored-by: Ben <ben@nousresearch.com>
2026-07-07 08:33:06 +00:00
Teknium 088b989442
fix(gateway): scope reset banners' session info to the serving profile (#59048 salvage) (#59329)
* fix(gateway): scope reset banners' session info to the serving profile

The auto-reset notice and the manual /reset //new banner both appended
_format_session_info() outside any profile scope, so a multiplexed
gateway advertised the base config's model/provider/context while the
session actually ran on the profile's.

Route both call sites through a new _reset_notice_session_info(source),
which enters _profile_runtime_scope for the source's profile when
gateway.multiplex_profiles is on (mirroring _run_agent's gating), so
_load_gateway_config()/_resolve_gateway_model() resolve the profile's
config.yaml via the existing context-local home override. Single-profile
gateways never enter the scope — behavior unchanged.

Both call sites invoke the helper via asyncio.to_thread: under the
scope, resolution can do blocking work (credential refresh,
context-length HTTP probes) that previously failed fast unscoped and
must not run on the event loop.

Fixes #59003

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

* chore(release): map irresi author email for PR #59048 salvage

---------

Co-authored-by: irresi <blueirobin02@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 18:25:45 +10:00
Ben Barclay 536ffedbf4
feat(docker): re-seed a terminally-dead Nous bootstrap session on boot (#59983)
The stage2-hook auth.json seed is first-boot-only ([ ! -f auth.json ]) to avoid
clobbering rotated refresh tokens on restart. That guard means a container whose
Nous bootstrap session took a terminal invalid_grant (tokens cleared,
providers.nous.last_auth_error.relogin_required stamped) cannot recover from a
restart — it stays unauthenticated until the credential is replaced.

Add a self-heal path: an orchestrator that manages the container supplies a
freshly-issued session via HERMES_AUTH_JSON_REBOOTSTRAP (distinct from the
create-only *_BOOTSTRAP var). On boot, scripts/docker_rebootstrap_nous_session.py
swaps ONLY the providers.nous entry, and ONLY when the on-disk entry is provably
terminal (quarantine marker + no usable tokens). Healthy/rotating/absent/
unparseable auth.json is always a no-op, so the env is safe to leave set across
restarts and never clobbers a good token. Pure stdlib, runs as its own
subprocess, always exits 0 so a re-seed error never fails the boot.

Reuses the same terminal predicate as get_nous_session_validity() so we re-seed
only a session that is genuinely dead.
2026-07-07 06:57:23 +00:00
kshitijk4poor ef599aa7f0 chore: map spiky02plateau in AUTHOR_MAP for #32824 salvage
The salvaged commits from #32824 use a bare
spiky02plateau@users.noreply.github.com (no numeric-id + prefix), which the
contributor-check.yml gate does not auto-resolve (it only skips the
<id>+<user>@users.noreply form). Add the explicit mapping so attribution CI
passes and release notes credit @spiky02plateau.
2026-07-07 12:01:33 +05:30
teknium1 8f80a982a6 chore: add fanyangCS to AUTHOR_MAP 2026-07-06 18:12:24 -07:00
teknium1 dd7198e71d chore: add tanmayxchoudhary to AUTHOR_MAP 2026-07-06 12:46:20 -07:00
Teknium 5e685999af
fix(ci): make the CI timing report unflakeable (#59818)
The 'CI timing report' job is pure observability — it collects per-job/step
durations from the GitHub API after the run and publishes an HTML gantt
report + PR-vs-main timing diff. It gates nothing (all-checks-pass does not
include it), yet it could redden a PR: the script makes dozens of paginated
API calls with the shared repo GITHUB_TOKEN and had zero retry handling, so
a single 403 (rate-limit burst when several PRs run CI concurrently) failed
the job. Observed twice in a row on PR #59805.

- api_get(): retry 403/429/5xx and connection errors with exponential
  backoff, honoring Retry-After / X-RateLimit-Reset (max 5 attempts, 120s
  cap). Non-transient statuses (404 etc.) still fail fast.
- main(): exhausted retries raise TimingsUnavailable, caught to emit a
  degraded summary line + placeholder HTML artifact and exit 0 — a metrics
  collector must never fail the PR's checks. No timings JSON is written on
  the degraded path so an empty baseline can never be cached.
- ci.yml: baseline-save steps on main skip gracefully when no JSON exists.

Verified with a mocked urlopen harness: retry-then-success (3 attempts),
exhausted-retries -> TimingsUnavailable, 404 fails fast without retry,
degraded main() exits 0 with summary + placeholder and no JSON, and the
--from-json happy path is unchanged.
2026-07-06 12:44:07 -07:00
teknium1 8cc1ca4ce2 chore: add bigstar0920 to AUTHOR_MAP 2026-07-06 12:43:59 -07:00
Hermes Agent 7426c09bee chore: map hellno in AUTHOR_MAP for #49033 salvage 2026-07-06 04:58:42 -07:00
teknium1 8235f484c9 feat(secrets): adapt 1Password onto the SecretSource interface
Follow-up on the cherry-picked #36896 commits, wiring 1Password into
the new registry as the reference *mapped* source:

- OnePasswordSource adapter (shape=mapped, scheme=op): fetch-only —
  precedence, override semantics, conflict warnings, and env writes
  move to the orchestrator; apply_onepassword_secrets kept as legacy
  shim like Bitwarden's.
- Registered in _ensure_builtin_sources; mapped op:// bindings now
  outrank bulk Bitwarden project dumps on contested vars.
- _cache.py FetchResult/is_valid_env_name re-exported from base so
  there is exactly one canonical definition; bitwarden.py re-adapted
  onto the contributor's DiskCache substrate.
- ErrorKind classification for op failures (auth/binary/empty/network).
- Registry + conformance coverage for OnePasswordSource, incl. the
  headline multi-source test: both vaults claim the same var, mapped
  1Password wins, conflict surfaced, provenance correct.
- env_loader tests migrated off the legacy apply_* mocks onto the
  fetch layer; AUTHOR_MAP entry for @hwrdprkns.
2026-07-06 04:58:07 -07:00
Hermes Agent 1fcf6e58b6 chore: map waseemshahwan in AUTHOR_MAP for #56841 salvage 2026-07-06 03:53:52 -07:00
Hermes Agent 09466971ff chore: map AIalliAI id-form noreply email in AUTHOR_MAP for #44222 salvage 2026-07-06 03:42:14 -07:00
teknium1 4df2536f21 chore: map Ahmett101 noreply email in AUTHOR_MAP for #59455 2026-07-06 03:28:30 -07:00
teknium1 8f849ea365 chore: credit isheng-eqi for #59446 in AUTHOR_MAP 2026-07-06 03:17:21 -07:00
teknium1 7e7e3af5b0 chore: map allenliang2022 in AUTHOR_MAP for #56932 test fold-in 2026-07-06 01:56:09 -07:00
kshitijk4poor c67aab763d chore: map isheng-eqi in AUTHOR_MAP for #59428 salvage
check-attribution CI fails on unmapped bare (non-noreply) contributor
emails. isheng-eqi's commit email (ishengeqi@163.com) has no + so it does
not auto-resolve — add the explicit mapping.
2026-07-06 13:47:18 +05:30
teknium1 f600dfca96 chore(release): map author emails for PR #56854/#57417 salvage 2026-07-05 21:48:59 -07:00
teknium1 43a4256320 fix(mcp): wake stale cached servers on session startup + AUTHOR_MAP
register_mcp_servers now nudges cached entries whose session is None
via _signal_reconnect, so a new agent session recovers a parked server
immediately instead of waiting up to _PARKED_RETRY_INTERVAL for the
next self-probe (#50170). Gate-check idea credit: @izumi0uu (#50184),
@LeonSGP43 (#37772), @Tranquil-Flow (#37899).
2026-07-05 21:48:36 -07:00
teknium1 5cc7c9b6a0 chore(release): map derek2000139 author email for PR #57838 salvage 2026-07-05 21:36:23 -07:00
teknium1 81becec45d chore: docs table entry + AUTHOR_MAP for preflight cluster salvage 2026-07-05 21:36:19 -07:00
konsisumer 18e840469f fix(install): guard Windows desktop installs against broken web_server 2026-07-05 19:35:30 -07:00
teknium1 b6f230b88e chore(release): map EdderTalmor author email for PR #41575 salvage 2026-07-05 19:13:20 -07:00
teknium1 b80b0b682a test(mcp): parked server self-probe revival + AUTHOR_MAP for #54139 salvage 2026-07-05 19:10:31 -07:00
teknium1 8a9e30dbd5 chore: AUTHOR_MAP entries for #54494/#56699 salvage 2026-07-05 19:10:00 -07:00
teknium1 6133285596 chore(release): map Alix-007 author email for PR #54620 salvage 2026-07-05 17:38:36 -07:00
teknium1 c5a8df3af2 chore(release): map jashlee+microsoft@microsoft.com -> s905060 (PR #57943 salvage) 2026-07-05 17:38:32 -07:00
Teknium 6fad6f1dd8
fix(whatsapp): contain and surface inbound media download failures (port nanoclaw#2895) (#59261)
Port from nanocoai/nanoclaw#2895's never-silently-drop guarantee.

Before: saveMedia() in scripts/whatsapp-bridge/bridge_helpers.js awaited
downloadMedia() with no try/catch. A failed CDN fetch (expired media URL,
transient network error — Baileys throws 'Failed to fetch stream from
https://mmg.whatsapp.net/...') rejected out of extractBridgeEvent, which
bridge.js awaits inside its messages.upsert for-loop with no per-message
guard — dropping the failed message AND every remaining message in the
same upsert batch, silently.

After:
- saveMedia catches download/write failures, records the media type, and
  logs a console.warn instead of rejecting.
- appendMediaFailureNote() (exported pure helper, mirroring the file's
  testable-helper convention) surfaces '[<type> could not be downloaded]'
  in the event body, so the agent learns media was sent rather than the
  attachment vanishing. Applied before the '[<type> received]' fallback
  so an uncaptioned failed image reads as a failure, not an arrival.

The reuploadRequest recovery half of nanoclaw#2895 is already wired in
bridge.js (downloadMediaMessage(..., { reuploadRequest:
sock.updateMediaMessage })); this ports the containment half hermes was
missing.

Tests: 3 new cases in bridge.native.test.mjs (note formatting, uncaptioned
failure containment, captioned failure note). All 5 bridge test files pass.
2026-07-05 17:21:28 -07:00
teknium1 2f2e60801c chore: add l0h1nth to AUTHOR_MAP for PR #32210 salvage 2026-07-05 14:41:57 -07:00
teknium1 e985e34659 chore: add falkoro to AUTHOR_MAP 2026-07-05 14:41:40 -07:00
Teknium 55e3ee1ab8
fix: remove dead f-string prefixes via ruff F541 (216 sites) (#52336)
ruff check --fix --select F541 . on current main. Pure prefix removals;
adjacent-string concatenations keep the f only on interpolating fragments.
No string content or live placeholder altered.
2026-07-05 13:42:46 -07:00
teknium1 b3b1e58ad6 fix(codex): stream commentary deltas through the reasoning channel
Follow-up to the salvaged #58696 (devatnull) + #41343 (annguyenNous)
commits: instead of fully suppressing commentary/analysis-phase stream
deltas, fire on_reasoning_delta so the CLI/gateway display them like
thinking text. Matches Codex CLI semantics where commentary is never
the turn's final answer, while keeping the narration visible in the
reasoning display. Adds devatnull to AUTHOR_MAP.
2026-07-05 06:29:45 -07:00
teknium1 372c0b5f45 chore: add devatnull to AUTHOR_MAP for PR #58700 salvage 2026-07-05 06:29:26 -07:00
teknium1 b0f2bdbe8b fix(whatsapp): gate poll-vote events to Hermes-created polls + salvage follow-ups
- bridge: only enqueue poll_update events for polls Hermes itself created
  (tracked via recentlySentIds when /send-poll returns) so arbitrary human
  polls in group chats don't inject agent-visible messages on every vote
- update test_already_whatsapp_italic for the new markdown-italic mapping
- AUTHOR_MAP entry for @devatnull (PR #58704 salvage)
2026-07-05 06:27:20 -07:00
devatnull 11627fdcb9 feat(whatsapp): native Baileys polls, clarify-as-poll, locations, and rich inbound metadata
Salvaged from PR #58704 by @devatnull, scoped to the WhatsApp surface:
- bridge_helpers.js: pure, tested extraction of inbound Baileys message
  parsing (quoted text, MIME/filename, PTT vs audio, stickers, contacts,
  reactions, polls, locations, GIF playback metadata)
- native poll primitive: /send-poll endpoint, poll messageSecret caching,
  encrypted vote decryption + aggregation via Baileys
- send_clarify() renders multi-choice clarify prompts as native polls;
  votes flow back through the existing clarify text-intercept
- send_location() + /send-location for native WhatsApp location pins
- structured quoted-reply context (fixes duplicated '[Replying to: ...]'
  rendered both by the adapter and gateway/run.py)
- outbound formatting: markdown *italic* -> WhatsApp _italic_, invisible
  unicode sanitization; execSync -> execFileSync hardening; GIF -> mp4
  gifPlayback conversion with truthful image/gif fallback

Out of scope (deliberately not salvaged from #58704): cross-platform
ordered-delivery machinery in gateway/platforms/base.py, LOCATION: and
hermes:poll response-text directives (no prompt wiring exists yet), and
the unconditional WhatsApp reply-anchor suppression.
2026-07-05 06:27:20 -07:00
teknium1 0ca2a927cf chore: add devatnull to AUTHOR_MAP for PR #58697 salvage 2026-07-05 06:12:49 -07:00
teknium1 95fc3c6b45 chore: add alastraz to AUTHOR_MAP for PR #41383 salvage 2026-07-05 05:36:31 -07:00
teknium1 dec4485d2f chore(release): AUTHOR_MAP entries for salvaged PR authors 2026-07-05 00:59:35 -07:00
teknium1 e4da3a7a52 chore(release): AUTHOR_MAP entry for salvaged PR author 2026-07-05 00:55:51 -07:00
teknium1 020a71678c chore(release): AUTHOR_MAP entries for salvaged PR authors 2026-07-05 00:48:50 -07:00
Teknium 6f052b7ff1
fix(copilot): set x-initiator per turn so user prompts bill as premium requests (salvage #4097) (#58544)
* fix(cli): set correct x-initiator header per Copilot turn

copilot_default_headers() always hardcoded x-initiator: agent, but
GitHub Copilot billing requires "user" for user-initiated prompts and
"agent" for tool/follow-up calls. This caused premium requests to never
be consumed correctly, risking billing issues or account bans.

Adds is_agent_turn param to copilot_default_headers() and injects
extra_headers={"x-initiator": "user"} on the first API call of each
user turn when targeting Copilot URLs. The flag flips to False after
injection so subsequent calls (tool use, streaming fallback) default
back to "agent".

Fixes #3040

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

* chore(release): add AUTHOR_MAP entry for @tjp2021 (PR #4097 salvage)

---------

Co-authored-by: Tim <tim@iteachyouai.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-05 00:44:15 -07:00
teknium1 d810ff2f2b chore(release): AUTHOR_MAP entries for salvaged PR authors 2026-07-05 00:41:34 -07:00
teknium1 10f7cb043c chore(release): AUTHOR_MAP entries for salvaged PR authors 2026-07-04 15:44:50 -07:00
teknium1 4ac8c7546b fix(vision): mkdir converted-PNG output dir; wire SVG pass-through to rasterizer; AUTHOR_MAP for #52688
- _normalize_to_supported_image: ensure cache/vision exists before writing
  the converted PNG (fresh HERMES_HOME had no dir -> FileNotFoundError).
- resolver: SVG passes through as image/svg+xml instead of erroring; the
  call sites rasterize to PNG via the salvaged normalize step (cairosvg /
  svglib / rsvg-convert / inkscape, best-effort with actionable error).
- normalization offloaded via asyncio.to_thread at both call sites.
- tests: resolver pass-through + rasterization + no-converter error paths.
- AUTHOR_MAP: jonathan@mintrx.com -> JAlmanzarMint (PR #52688 salvage).
2026-07-04 15:30:50 -07:00
teknium1 6fcd470d54 chore(release): AUTHOR_MAP entries for salvaged PR authors 2026-07-04 15:18:41 -07:00
Teknium e670d9cdd6
Merge pull request #58489 from NousResearch/revert-30179
Revert "feat(egress): iron-proxy credential-injection firewall" (#30179)
2026-07-04 13:41:24 -07:00
teknium1 f9c543d6d1 chore(release): add AUTHOR_MAP entry for @danilofalcao (PR #56674 salvage) 2026-07-04 13:40:59 -07:00
teknium1 c6dc7c03c3
Revert "Merge pull request #30179 from NousResearch/feat/iron-proxy"
This reverts commit 8790adc4c6, reversing
changes made to fe5054bccf.
2026-07-04 13:38:59 -07:00
Teknium 8790adc4c6
Merge pull request #30179 from NousResearch/feat/iron-proxy
feat(egress): iron-proxy credential-injection firewall for sandboxes
2026-07-04 13:29:23 -07:00
kshitijk4poor 60906be3fc chore: map yingwaizhiying@gmail.com -> msh01 in AUTHOR_MAP
Contributor of the salvaged PR #58276 fix commit. Required so the
contributor attribution CI check passes on the rebase-merge that
preserves their authorship.
2026-07-04 21:04:27 +05:30
kshitijk4poor a37fd66dec fix(telegram): shut down abandoned init app + AUTHOR_MAP + cover the deadline helper
Follow-up to @msh01's wall-deadline init-timeout fix.

- Resource leak: on timeout the initialize() task is abandoned without
  awaiting its (shielded, possibly-never-completing) cancellation, so the
  half-built PTB app's httpx client / connection pool was never closed —
  up to 8x across the retry ladder. Add an optional on_abandon cleanup to
  _await_with_thread_deadline that best-effort app.shutdown()s the abandoned
  app, run detached + exception-swallowed so it can never re-block or re-hang
  the ladder (mirrors _close_client_on_timeout in agent/auxiliary_client.py).
- Cover the helper itself: the salvaged test monkeypatched out the real
  _await_with_thread_deadline, so its abandonment/cleanup path was untested.
  Add direct tests for happy-path return, prompt-timeout-with-cleanup, and
  cleanup-error-swallowed; the wedged coroutines swallow cancellation for a
  bounded window (proving the helper returns before cancellation completes,
  the #58236 shielded-scope behavior) without leaving an immortal task that
  would wedge pytest teardown. Widen the salvaged stub to accept on_abandon.
- Attribution: add yingwaizhiying@gmail.com -> msh01 to AUTHOR_MAP (bare
  gmail does not auto-resolve the check-attribution gate).

Known follow-up (not addressed here): the retry ladder reuses the same
self._app across all 8 attempts; a fresh app per attempt would fully close
the coherence risk if an abandoned initialize() completes in the background.
That is a larger restructure of the ~130-line builder+handler setup, left
for a separate change.
2026-07-04 20:45:48 +05:30
teknium1 14cbbd541e
Merge remote-tracking branch 'origin/main' into iron-proxy-followups
# Conflicts:
#	hermes_cli/config.py
#	hermes_cli/main.py
#	website/docs/reference/cli-commands.md
2026-07-04 03:09:43 -07:00
teknium1 ca596e228c chore: map marxb@protonmail.com to Marxb85 in AUTHOR_MAP 2026-07-04 02:37:33 -07:00
kshitijk4poor 3c8c968d16 chore: map huanshan5195 in AUTHOR_MAP for #57601 salvage
check-attribution requires every contributor author email to be in
AUTHOR_MAP; the salvaged commit is authored by
huanshan5195 <huanshan5195@users.noreply.github.com>.
2026-07-04 14:19:44 +05:30
kshitijk4poor 8229d7765a chore: map lavya@loom.local -> LavyaTandel in AUTHOR_MAP
Salvage of PR #57893 (envelope-layout prompt-cache marker fix, #57845)
uses the contributor's local git identity lavya@loom.local, which is not
GitHub-resolvable. Add the mapping so contributor_audit passes when the
salvage PR lands.
2026-07-04 13:54:27 +05:30
kshitijk4poor a3ea73932a chore(release): map iacobs@webflakes.com -> m0n5t3r in AUTHOR_MAP
Second contributor identity from PR #28652 (issue #28651).
2026-07-03 20:14:27 +05:30
kshitijk4poor ad3261bc77 chore: add AUTHOR_MAP entry for PR #57692 salvage (CocaKova) 2026-07-03 18:38:12 +05:30
Teknium 87ae4ae94b
fix(update): harden #57659 follow-ups — task restore on failure, --force-venv split, trampoline detection, managed-install health (#57680)
Five follow-ups to #57659 from post-merge review:

1. install.ps1: gateway scheduled-task re-enable now runs in a finally
   (a thrown Remove-Item/uv venv failure previously stranded the user's
   gateway autostart disabled), and tasks that were already disabled
   before the install are no longer blindly re-enabled.
2. The venv-python holder guard is no longer bypassed by plain --force
   (which the desktop bootstrap passes on every update while its lock
   probe only checks hermes.exe/app.asar). New explicit --force-venv is
   the escape hatch; --force keeps bypassing only the hermes.exe shim
   guard.
3. _detect_venv_python_processes now also catches uv/base-interpreter
   trampolines whose exe is outside the venv, via cmdline (venv path or
   '-m hermes_cli.main' tied to this install root) and cwd.
4. Missing venv python is now UNHEALTHY on managed installs
   (.hermes-bootstrap-complete / .update-incomplete markers) so the
   repair lane runs instead of 'Already up to date!'; the repair branch
   recreates the venv first when it's gone entirely. Dev checkouts keep
   reporting healthy.
5. install.ps1 comment no longer claims a Startup-folder disarm the
   code doesn't perform (logon-only, not a mid-install respawner).
2026-07-03 04:08:37 -07:00
teknium1 0e9136cb27 chore: add suninrain086 to AUTHOR_MAP for salvaged #50685 2026-07-03 03:54:01 -07:00
Teknium b14d75f8af
fix(update): prevent and self-heal half-updated venvs on Windows (#57659)
Root-causes the July 2026 Windows incident chain (locked _brotlicffi.pyd /
_sodium.pyd during install, then 'No module named annotated_doc' with
'hermes update' insisting 'Already up to date!'):

- hermes update: probe venv core imports even when the checkout is current;
  a half-updated venv (dep sync killed mid-flight by a locked .pyd) is now
  detected and repaired instead of being reported as up to date
- hermes update (Windows): after pausing gateways, refuse to mutate the venv
  while other processes run from the venv interpreter (the Desktop backend
  runs as python.exe so the hermes.exe shim guard never saw it); --force
  keeps the old behavior
- install.ps1 venv stage: disarm gateway autostart Scheduled Tasks before
  the kill sweep (they respawn the gateway inside the kill->delete window),
  make the sweep a bounded loop requiring 3 clean passes, and rename-then-
  delete the old venv (a rename succeeds even with mapped DLLs) with stale-
  dir cleanup on the next run
- desktop updater: 'venv shim still locked after 15s' now ABORTS the update
  hand-off (restarting our backend, surfacing the holder to the user)
  instead of 'proceeding anyway (force)' into guaranteed venv corruption;
  the unlock wait also re-kills respawned backends each poll tick
2026-07-03 03:24:08 -07:00
kshitijk4poor 0950dae2fa Merge remote-tracking branch 'upstream/main' into HEAD
# Conflicts:
#	scripts/release.py
2026-07-03 03:52:15 +05:30
kshitijk4poor 26edfab004 chore: add trismegistus-wanderer to AUTHOR_MAP for PR #31856 salvage 2026-07-03 03:30:54 +05:30
kshitijk4poor eb806c7f50 chore(release): add infinitycrew39 to AUTHOR_MAP (#56431 salvage) 2026-07-03 03:27:32 +05:30
kshitijk4poor de67f430b2 chore: map gumclaw@gumroad.com in AUTHOR_MAP for PR #57322 salvage 2026-07-03 03:26:53 +05:30
Brooklyn Nicholson ab942330fc chore(release): map yingliang-zhang in AUTHOR_MAP for #57335 2026-07-02 15:44:37 -05:00
David Zhang 30e947e0a0 feat(gateway): persist per-session /model overrides across gateway restarts
Per-session /model overrides (_session_model_overrides) were in-memory only,
so a gateway restart silently reverted every session to the global default
model. Persist the non-secret parts (model/provider/base_url ONLY — never
api_key) into the session entry in sessions.json and lazily rehydrate them
on first use after a restart, re-resolving credentials through the normal
runtime provider resolution.

- gateway/session.py: SessionEntry.model_override field with
  sanitize_model_override() (allowlist: model/provider/base_url) applied on
  both serialization and deserialization; SessionStore.set_model_override /
  get_model_override accessors. reset_session() already creates a fresh entry,
  so /new keeps its clear-on-reset semantics — a restart cannot resurrect an
  override the user reset away.
- gateway/slash_commands.py: write-through at both /model set sites (text
  command + picker) after storing the in-memory override.
- gateway/run.py: _rehydrate_session_model_override() called from
  _resolve_session_agent_runtime(); in-memory state always wins, credentials
  are re-resolved per provider (credential-less fallback on failure). Session
  expiry finalization also drops the persisted override.
- tests/gateway/test_session_model_override_persistence.py: restart
  round-trip, /new clearing, api_key-never-serialized (including tampered
  sessions.json), rehydration + live-state precedence + credential-failure
  degradation.

Salvaged from #3659 by @Git-on-my-level, narrowed to the restart-persistence
gap confirmed in triage.
2026-07-02 05:51:12 -07:00
Jneeee b98baa3039 feat(config): extra HTTP headers for LLM API calls (#3526 salvage)
Named providers / custom_providers entries in config.yaml now accept an
extra_headers dict scoped to that endpoint — for reverse proxies, API
gateways, and custom auth schemes (e.g. Cloudflare Access service tokens).

- hermes_cli/config.py: normalize extra_headers on provider entries
  (_normalize_custom_provider_entry + providers-dict translation), add
  get_custom_provider_extra_headers /
  apply_custom_provider_extra_headers_to_client_kwargs helpers keyed on
  base_url (case/trailing-slash insensitive, no substring bypass —
  mirrors the TLS helpers)
- hermes_cli/runtime_provider.py: surface extra_headers in the resolved
  runtime for named custom providers (providers dict, legacy
  custom_providers list, and the credential-pool path)
- run_agent.py / agent/agent_init.py: merge per-provider extra_headers
  onto the OpenAI client default_headers at construction and on every
  _apply_client_headers_for_base_url re-application (credential swaps,
  rebuilds), most-specific level wins; OpenAI-wire only (native
  Anthropic/Bedrock scoped out)
- agent/auxiliary_client.py: accept model.extra_headers as an alias of
  model.default_headers for the global variant
- cli-config.yaml.example: documented commented example
- Header values are treated as secrets and never logged

Salvaged from PR #3526 by @jneeee, reimplemented against current main.

Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
2026-07-02 05:33:25 -07:00
Mibayy 4a09b692ec feat(api-server): per-client model routing via model_routes (#3176 salvage)
Adds a no-code routing layer to the OpenAI-compatible API server so one
Hermes deployment can map different API clients to different
model/provider backends. Clients pick a backend by sending a configured
alias as the OpenAI 'model' field; unmatched values fall back to the
global model. Configured aliases are listed by GET /v1/models.

Precedence (highest first): session /model override > model_routes
route > global config. Route provider credentials resolve through
_resolve_runtime_agent_kwargs_for_provider (same seam as
channel_overrides); per-route api_key/base_url are upstream provider
credential overrides — never caller auth, never logged.

Salvaged and rebased from PR #3176 by @Mibayy onto current main.
2026-07-02 05:23:28 -07:00
Mibayy ce9aa869fc feat(commands): /compact alias + --preview/--dry-run flags for /compress (#3243 salvage)
Salvaged from PR #3243 by @Mibayy, reimplemented against current main
(the original diff targeted a removed gateway/run.py handler).

- /compact is now a first-class alias of /compress (CLI, gateway,
  Telegram/Slack/Discord command lists, autocomplete) — also fixes the
  dangling '/compact' references in gateway error messages
  (gateway/run.py context-exhausted banners).
- --preview / --dry-run: report what WOULD be compressed (message
  counts, token estimate, 'here [N]' boundary) without touching the
  transcript. Flags coexist with the existing 'here [N]' / focus-topic
  args on both the CLI and gateway surfaces via shared pure helpers in
  hermes_cli/partial_compress.py.
- --aggressive (LLM-free hard truncation) is intentionally NOT
  implemented: it would need its own transcript-persistence branch
  outside the guarded _compress_context rotation machinery (#44794
  data-loss class). The flag is recognized and returns an explanatory
  message pointing at '/compress here [N]' and /undo instead of being
  mis-parsed as a focus topic.
- locales: gateway.compress.aggressive_unsupported added to all 16
  catalogs (parity test enforced).
- release.py: AUTHOR_MAP entry for contributor credit.
2026-07-02 05:10:31 -07:00
Morgan K 39bff67957 feat(gateway): add 'log' option to display.tool_progress
Salvage of #3459 by @keslerm, reimplemented against the restructured
progress-callback block in gateway/run.py (resolve_display_setting,
needs_progress_queue, thinking-relay). Duplicate PR #3458 by @dlkakbs was
submitted 4 minutes earlier with the same feature — both credited.

Co-authored-by: Dilee <uzmpsk.dilekakbas@gmail.com>

tool_progress: log keeps the chat silent and appends timestamped tool-call
lines to ~/.hermes/logs/tool_calls.log via a dedicated queue drained by an
async writer (RotatingFileHandler 5MB x 3, RedactingFormatter so secrets
never land on disk). Gateway-only by design; thinking_progress relaying and
the webhook gate are unaffected. /verbose now cycles
off -> new -> all -> verbose -> log.
2026-07-02 05:09:38 -07:00
Mibayy 070ac2a719 fix(status): label provider as custom when config.yaml model.base_url is set
Salvage of the surviving hunk of #3296 by @Mibayy. The PR's gateway
_handle_provider_command hunk targets code removed on main (/provider was
absorbed into /model + /status, which already read model.base_url); the
hermes status mislabel was the remaining live symptom:
_effective_provider_label() only checked the legacy OPENAI_BASE_URL env var,
so a custom endpoint configured canonically in config.yaml still displayed
as OpenRouter.
2026-07-02 04:59:02 -07:00
Teknium 44650a5ce3 chore: add AUTHOR_MAP entry for @ajmeese7 (#3219 salvage) 2026-07-02 04:48:02 -07:00
Roger Smith c0d694a492 fix(whatsapp): resolve LID sender IDs to phone numbers in bridge message payload
WhatsApp has migrated to Linked Identity Device (LID) format for user
IDs (e.g. 244645917392975@lid instead of 18505551234@s.whatsapp.net).

The bridge already resolves LIDs to phone numbers for its own allowlist
check via buildLidMap(), but the senderId field in the message payload
sent to the gateway still contained the raw LID. This caused the
gateway's WHATSAPP_ALLOWED_USERS check to reject all messages as
unauthorized, since the LID numbers don't match the phone numbers in
the allowlist.

Fix: resolve LID → phone in the senderId, senderName, and chatName
fields of the event payload before sending to the gateway, using the
existing lidToPhone mapping.
2026-07-02 04:48:02 -07:00
kshitijk4poor be21e06ab3 chore(release): map ai-lab@foxmail.com to CrazyBoyM
Adds the AUTHOR_MAP entry for CrazyBoyM (ai-lab@foxmail.com) so the
contributor-attribution CI check passes when PR #55828's commits are
rebase-merged with authorship preserved.
2026-07-02 16:55:02 +05:30
Tarun Ravikumar 2068754d6f feat(api-server): inline MEDIA: image tags as base64 data URLs for remote frontends
Salvage of the surviving piece of #2696 by @tarunravi. The PR's other two
changes (tool progress streaming, SSE None-sentinel fix) were independently
superseded on main by the structured hermes.tool.progress SSE events and the
rewritten queue-drain loop.

Remote OpenAI-compatible frontends can't read server-local file paths, so
MEDIA:<path> tags (browser screenshots, generated images) were dead text.
_resolve_media_to_data_urls() now inlines small (<=5MB) local images as
markdown data URLs across all four response surfaces: chat completions
(non-streaming), session chat, session chat stream final event, and the
Responses API. Non-image, missing, or oversized paths pass through
untouched.
2026-07-02 03:23:44 -07:00
CharmingGroot 88bd1c01e1 fix(email): harden adapter against malformed IMAP responses
Salvage of #2794 by @CharmingGroot, ported to the relocated
plugins/platforms/email/adapter.py:

- Guard raw_email = msg_data[0][1] against IndexError/TypeError and
  non-bytes payloads. UIDs are added to _seen_uids before fetch, so an
  exception mid-batch permanently skipped every remaining message in
  the batch — now the bad message is logged and skipped instead.
- Message-ID domain generation falls back to 'localhost' when
  EMAIL_ADDRESS lacks '@' (now via a shared _message_id_domain() helper
  covering all 3 send paths; the PR fixed 2 of 3).
2026-07-02 03:12:53 -07:00
crazywriter1 c43aa6301d feat(gateway): per-channel model and system prompt overrides (Fixes #1955)
- ChannelOverride + channel_overrides; session /model > channel > global
- Thread/parent lookup; YAML bridge for discord.channel_overrides
- Guard channel_overrides when config lacks platforms (test mocks)
- Add sampiyonyus@gmail.com to AUTHOR_MAP
2026-07-02 03:08:11 -07:00
Teknium 6546c58641 chore: add AUTHOR_MAP entry for @VolodymyrBg (#2861 salvage) 2026-07-02 03:00:17 -07:00
teknium1 8b1ad38ecb chore: add AUTHOR_MAP entry for @sahibzada-allahyar (#39227 salvage) 2026-07-02 01:58:19 -07:00
kshitijk4poor 71c0622122 chore(release): map kiljadn@gmail.com to designnotdrum for #56480 salvage
Attribution audit gate: the salvaged contributor commits carry
kiljadn@gmail.com (Nick Mason / @designnotdrum). Add the mapping so
contributor_audit.py resolves the author on this PR.
2026-07-02 13:25:25 +05:30
kshitijk4poor e2ffbf0cf4 chore(release): add AUTHOR_MAP entries for compression-routing salvage
Map the two contributor emails whose commits are cherry-picked into the
compression-routing-integrity salvage so scripts/contributor_audit.py
attributes them at release time:

- jvsantos.cunha@gmail.com -> plcunha (PR #55300)
- jakepresent1@gmail.com   -> jakepresent (PR #55721)

r266-tech (PR #50517) is already mapped.
2026-07-02 12:49:42 +05:30
Teknium 7c1a029553
chore: release v0.18.0 (2026.7.1) (#56611) 2026-07-01 13:07:40 -07:00
kshitijk4poor 148674e27c chore: add AUTHOR_MAP entry for zmlgit (PR #54872 salvage) 2026-07-01 22:42:58 +05:30
nankingjing 5eaccf5802 fix(gateway): queue interrupts during in-flight context compression
With the default busy_input_mode=interrupt, a burst of rapid gateway
messages arriving while context compression is in flight could interrupt
the current turn and start a fresh turn against the pre-rotation parent
session. Because compression is interrupt-immune (#23975), the still-
running compression later rotates the id out from under that new turn,
and if the new turn also grew past the compression threshold it started
its own uncancellable compression on the same stale parent — forking
multiple orphaned one-shot sibling continuations (#56391).

While a state.db compression lock is held for the session, demote
'interrupt' busy-input mode to 'queue' semantics (mirroring the subagent
protection in #30170), so the follow-up message waits for the in-flight
compression + its id rotation to land instead of racing a new turn
against the stale parent. Ack copy explains the compression demotion.

Fixes #56391.
2026-07-01 06:38:24 -07:00
kshitij 7b45a22ddf
Merge pull request #56382 from kshitijk4poor/chore/authormap-gromykoss-56372
chore: add Gromykoss to AUTHOR_MAP for PR #56372 salvage
2026-07-01 17:59:06 +05:30
Steve Lawton c73e74386b feat(vertex): add Google Vertex AI provider for Gemini (OAuth2)
Adds Vertex AI as a first-class provider for Gemini models via Vertex's
OpenAI-compatible endpoint. Vertex authenticates with short-lived OAuth2
access tokens (service-account JSON or ADC), not a static API key — the
missing piece behind the recurring requests (#13484, #12639, #56259).

- agent/vertex_adapter.py: OAuth2 token minting + refresh-on-expiry
  (5-min margin), ADC->service-account fallback, global vs regional
  endpoint URLs. Config precedence: env var > config.yaml > default.
- plugins/model-providers/vertex/: provider profile (auth_type=vertex),
  reuses Gemini's extra_body.google.thinking_config translation.
- runtime_provider: vertex short-circuit BEFORE the credential pool so a
  credentials-file path is never mistaken for a static API key; mints a
  fresh token + computes base_url per resolve.
- run_agent + conversation_loop: _try_refresh_vertex_client_credentials()
  re-mints the token and rebuilds the client on a mid-session 401, so a
  long-lived gateway agent survives token expiry (~1h).
- auxiliary_client: vertex auth_type branch for side-LLM tasks.
- config.yaml: vertex.project_id / vertex.region (non-secret, bridged to
  env); credential path stays in .env (VERTEX_CREDENTIALS_PATH).
- setup wizard + model picker: dedicated _model_flow_vertex; curated
  google/gemini-* model list; --provider choices.
- pricing/metadata: Vertex prices off the gemini docs snapshot; endpoint
  host auto-maps to the vertex provider (no probe spam).
- lazy_deps + pyproject [vertex] extra: google-auth, opt-in only.
- docs: guides/google-vertex.md + providers page; tests for adapter +
  runtime resolution.

Salvages and modernizes #8427 by @slawt onto current main: rewired from
the legacy PROVIDER_REGISTRY path to the provider-profile architecture,
moved non-secret config out of .env into config.yaml, and added the
per-turn 401 token-refresh the original lacked.
2026-07-01 05:25:33 -07:00
kshitijk4poor 0ebbfbcc84 chore: add Gromykoss to AUTHOR_MAP for PR #56372 salvage
Maps gromyko.ss83@gmail.com -> Gromykoss so the contributor-attribution
audit passes for the #56372 salvage (context_compressor END MARKER reorder).
2026-07-01 17:52:57 +05:30
teknium1 2f167a2b84 fix: comment accuracy + AUTHOR_MAP for salvaged PR #50204
- Correct the exit-75 comment: Hermes-generated units set
  StartLimitIntervalSec=0 (rate limiting disabled), so StartLimitBurst
  does not bound loops. The real bound is that genuine crashes exit
  non-zero-but-not-75, and RestartForceExitStatus=75 only whitelists
  the planned code.
- Add randomuser2026x AUTHOR_MAP entry (CI blocks unmapped emails).
2026-07-01 05:13:03 -07:00
Brett 9f03095044 fix(telegram): cap initialize() with per-attempt timeout so unreachable fallback IPs can't hang startup
Wrap each Telegram initialize() attempt in asyncio.wait_for(HERMES_TELEGRAM_INIT_TIMEOUT,
default 30s). When api.telegram.org and all fallback IPs are unreachable, the connect
chain has no outer bound, so a single initialize() blocks for minutes and the
retry-on-exception loop never fires — the gateway appears to hang after the banner.
The timeout guarantees each attempt is bounded, then retries with backoff, then fails
with an actionable error. Also adds WARNING-level progress logs before DoH discovery
and each connect attempt (visible at default log level).

Salvaged onto plugins/platforms/telegram/adapter.py (Telegram moved from
gateway/platforms/ since the PR was opened). Adds env var to docs + AUTHOR_MAP.

Co-authored-by: Hermes Agent <127238744+teknium1@users.noreply.github.com>
2026-07-01 05:07:10 -07:00
teknium1 050e602de3 chore(release): map HODLCLONE author for PR #49351 salvage 2026-07-01 05:06:00 -07:00
teknium1 cfbc7ed1f9 fix(browser): narrow credential-query denylist to unambiguous names
Follow-up on the salvaged #49830 hardening. The contributor's sensitive
query-param set included bare English words (code, key, auth, session,
sig) that double as ordinary page facets — ?code= on promo/challenge
pages, ?key= as a search facet, ?session= on blogs — so web_extract and
cloud browser_navigate would refuse a large slice of normal browsing.

Narrow the set to unambiguously credential-named params (access_token,
authorization, client_secret, password, token, x-amz-signature, ...).
Prefix-based vendor-key redaction (is_safe_url) still catches recognizable
key shapes; this set is the belt-and-suspenders for opaque secrets carried
under an explicit credential-named parameter.

Also fixes two intra-PR-staleness test breakages surfaced by salvaging onto
current main:
- web_extract_tool() no longer accepts use_llm_processing= (signature
  changed since the PR was authored) — dropped the invalid kwarg.
- agent.redact now fully masks keyed 'token=<secret>' to 'token=***'
  instead of partial 'sk-...'; the console-redaction test now asserts the
  real invariant (secret body gone) rather than the exact mask format.

Added a regression test that generic English-word query params are NOT
blocked by the credential guard.
2026-07-01 05:04:41 -07:00
teknium1 3b41df6d46 test(gateway): regression for multi-profile node symlink leak; AUTHOR_MAP
Add tmp_path symlink regression tests for both generate_systemd_unit and
generate_launchd_plist (~/.local/bin/node -> profile node install must not
leak the profile target into the generated unit PATH). Register
jearnest11's AUTHOR_MAP entry for the salvage cherry-pick.
2026-07-01 04:57:21 -07:00
teknium1 55c8b2c81f chore(release): add AUTHOR_MAP entry for udatny (#29433 salvage) 2026-07-01 04:55:15 -07:00
Zeheng Huang 4c2c54c78c fix(matrix): await inbound sync handlers
Register the Matrix room-message, reaction, and invite handlers with
mautrix's wait_sync=True. mautrix's handle_sync() only returns the tasks
for handlers registered as sync-awaited; non-waited handlers are
fire-and-forget via background_task.create() and are NOT returned. Since
_dispatch_sync() awaits only the returned tasks (await asyncio.gather),
the inbound handlers previously had no completion point, so Tuwunel/
mautrix homeservers connected and completed initial sync but dispatched
zero inbound messages.

Fixes #46142.

Co-authored-by: Zeheng Huang <153708448+hunjaiboy@users.noreply.github.com>
2026-07-01 04:42:33 -07:00
kshitij d4e8c358c0
Merge pull request #56330 from kshitijk4poor/chore/authormap-valenteff
chore: add AUTHOR_MAP entry for valenteff (#53277 salvage)
2026-07-01 16:49:16 +05:30
shawchanshek 3b739b990b fix(title_generator): strip think blocks from LLM output before extracting title
Think-enabled models (MiniMax M2.7, DeepSeek, etc.) emit inline
<think>...</think> reasoning even for simple prompts like title
generation, and the raw XML was leaking into session titles. Route the
title-model response through the canonical strip_think_blocks scrubber
before cleanup so every tag variant — closed pairs, unterminated blocks,
orphan closes, mixed case — is handled, not just a single literal
<think> pair.

- 2 regression tests: closed <think> pair stripped, unterminated block
  at start yields no title.

Salvaged from PR #44126 by @shawchanshek.
2026-07-01 04:18:48 -07:00
kshitijk4poor 9c870548e3 chore: add AUTHOR_MAP entry for valenteff (#53277 salvage) 2026-07-01 16:44:07 +05:30
kshitijk4poor b3f55c2037 chore: add AUTHOR_MAP entries for session-persistence salvage batch
Maps the two plain-email contributors whose PRs are being salvaged so
contributor_audit.py passes:
- info@djimit.nl -> djimit (PR #48034)
- lubos@komfi.health -> lubosxyz (PR #49225)

The other two PRs in the batch (#50405 sasquatch9818, #48764 srojk34)
use users.noreply.github.com emails, which check-attribution auto-skips.
2026-07-01 16:38:56 +05:30
kshitij 8415c4703a
Merge pull request #56317 from kshitijk4poor/chore/authormap-bitcryptic
chore: add AUTHOR_MAP entry for bitcryptic-gw (#53997 salvage)
2026-07-01 16:33:47 +05:30
Tao Chen d3c8667462 fix(slack): authorize bot/workflow senders before the no-user-id guard
Slack Workflow Builder posts (and other app/bot messages) arrive as
subtype=bot_message with user=None. _is_user_authorized rejected them at
the `if not user_id: return False` guard, which runs *before* the #4466
{PLATFORM}_ALLOW_BOTS bypass — so @mentioning the bot from a Slack
workflow silently did nothing, even with SLACK_ALLOW_BOTS (or
SLACK_ALLOW_ALL_USERS) set. The chat-scoped allowlist for Telegram/QQ
already runs before that guard for the same reason (channel broadcasts
with no from_user); Slack was both missing from the bot-bypass map and
had the bypass running too late.

- gateway/authz_mixin: move the {PLATFORM}_ALLOW_BOTS bypass ahead of the
  no-user-id guard and add Platform.SLACK -> SLACK_ALLOW_BOTS.
- plugins/platforms/slack/adapter: set is_bot=True on inbound
  bot_message events so the gateway can identify workflow/app senders
  (they carry no user_id to match against the allowlist).

Tested: new tests/gateway/test_slack_bot_auth_bypass.py plus the existing
Discord/Feishu bot-auth and gateway authz/gating suites all pass.
2026-07-01 16:32:32 +05:30
kshitijk4poor fcbf850f33 chore: add AUTHOR_MAP entry for bitcryptic-gw (#53997 salvage) 2026-07-01 16:28:15 +05:30
teknium1 27347b2239 fix(gateway): align resume safety-net note with canonical recovery wording
Follow-up on the salvaged resume_pending fix: the empty-turn safety net
now emits the same reason-aware recovery note as the _is_resume_pending
branch (reason phrase + 'session restored' guidance + no-re-execute
instruction) instead of a second, differently-worded note. Also adds the
AUTHOR_MAP entry for the salvaged commit.
2026-07-01 03:57:44 -07:00
teknium1 49a87bcd1e chore(release): map SahilRakhaiya05 contributor email for #44073 salvage 2026-07-01 03:56:28 -07:00
Swissly 242c9639a8 fix(cron): prevent multi-target delivery loop crash on per-target failure
The standalone thread-pool fallback in _deliver_result() runs inside the
`except RuntimeError:` block (taken when asyncio.run() sees a running loop).
When future.result() raised there (SMTP ConnectionError, timeout, etc.), the
exception was NOT caught by the sibling `except Exception:` — it escaped
_deliver_result() and crashed the whole delivery loop, silently skipping every
remaining target. Multi-target delivery (e.g. deliver: 'email:a,email:b') is a
documented feature, so this broke a promised contract.

Wrap the fallback in its own try/except so a per-target failure is logged with
exc_info and the loop continues to the next target.

Fixes #47163
2026-07-01 03:48:37 -07:00
teknium1 5c2dccd06f chore(release): map kangsoo-bit author for PR #47508 salvage 2026-07-01 03:42:32 -07:00
teknium1 9dd6451c80 chore(release): add WXBR to AUTHOR_MAP for #46183 salvage 2026-07-01 03:34:49 -07:00
YLChen-007 e23f723389 fix: make streaming reasoning-tag filter case-insensitive
The streaming think-tag suppressors in cli.py (_stream_delta) and
gateway/stream_consumer.py (_filter_and_accumulate) matched tag names
with case-sensitive str.find(), so only the exact-case literals in the
tag tuples were caught. Mixed-case variants a model may emit — <Think>,
<ThInK>, <REASONING>, <Thought> — slipped through and leaked raw
reasoning into the user-visible stream.

Match against a lowercased view of the buffer with lowercased tag names
at all three sites (open-tag boundary search, partial-tag hold-back,
close-tag search) in both paths. Only KNOWN tag names are matched — no
substring matching — and the block-boundary gating that protects prose
mentions of <think> is preserved.

- 6 parametrized case-insensitive regression tests in each of
  tests/gateway/test_stream_consumer.py and
  tests/cli/test_stream_delta_think_tag.py.

Salvaged from PR #27289 by @YLChen-007.
2026-07-01 03:25:02 -07:00
teknium1 1c350728ec chore(release): map Lazymonter into AUTHOR_MAP for PR #42914 salvage 2026-07-01 03:21:20 -07:00
Teknium 2b8adb8683 chore(release): map tgmerritt author for PR #43553 salvage 2026-07-01 03:17:48 -07:00
testingbuddies24 e07768a53f fix(gateway): strip orphan think-tag close tags in progressive stream
When a model emits an inline <think>...</think> block but the opening
tag is dropped upstream (thinking-mode toggle, truncated stream, or
incomplete upstream filtering), the bare </think> close tag leaked
through to the user in the live progressive edit. The agent-side final
scrubber (agent/think_scrubber.py) already had _strip_orphan_close_tags;
this ports the same logic into GatewayStreamConsumer so the streaming
display stays clean too.

- _filter_and_accumulate: strip orphan close tags before appending the
  'no-opening-tag' branch text to _accumulated.
- _flush_think_buffer: same on stream end for held-back partials.
- 14 regression tests (TestStripOrphanCloseTags): all 6 close-tag
  variants, multi-tag, partial-tag-untouched, trailing whitespace,
  and end-to-end through _filter_and_accumulate / _flush_think_buffer.

Only strips KNOWN close-tag names (case-insensitive) — never arbitrary
tag-shaped substrings — so comparison operators and unrelated prose are
preserved.

Salvaged from PR #43192 by @testingbuddies24.
2026-07-01 03:04:01 -07:00
Wing Huang 828f33e6b1 fix(ci): map contributor email for attribution check
scripts/release.py AUTHOR_MAP is greped by the Contributor Attribution
Check to resolve a commit author's email -> GitHub username. Add
huangsen365@gmail.com -> huangsen365 so this PR's commits pass the check.

(This commit originally also carried a gateway race-test flake fix; that
edit is now dropped because main independently hardened the same test with
a superior server._sessions snapshot/restore isolation, making ours
redundant.)
2026-07-01 02:51:45 -07:00
teknium1 d15a288812 chore(release): map arthurzhang author for PR #34718 salvage 2026-07-01 02:45:22 -07:00
mrparker0980 10a54ccc2c fix(security): anchor @file context refs to canonical read deny-list
`@file` / `@folder` context-reference expansion enforced its own narrow
deny-list (`_ensure_reference_path_allowed` in `agent/context_references.py`)
that only covered `~/.ssh` keys, a handful of shell dotfiles, `~/.hermes/.env`,
and `skills/.hub`. It never blocked the credential stores that the canonical
read guard (`agent/file_safety.get_read_block_error`) protects: provider API
keys (`~/.hermes/auth.json`), Anthropic OAuth tokens
(`~/.hermes/.anthropic_oauth.json`), MCP OAuth material (`~/.hermes/mcp-tokens/`),
webhook HMAC secrets, and project-local `.env` files.

This matters because the messaging gateway feeds **untrusted** remote text
straight into reference expansion: `gateway/run.py` calls
`preprocess_context_references_async(..., allowed_root=_msg_cwd)` where
`_msg_cwd` defaults to the operator's HOME when `TERMINAL_CWD` is unset. A chat
peer (Telegram/Discord/Slack/...) could send `@file:~/.hermes/auth.json`, pass
the `allowed_root` check (it resolves under HOME), slip past the narrow list,
and have the operator's live keys read into the agent's context — where the
model would typically echo or act on them.

Rather than duplicate and re-sync a second secret list, this routes the guard
through the existing single source of truth. A reviewer might ask "why not just
add `auth.json` to the local list?" — because the local list has already drifted
once (a prior commit had to add `.config/gh`); anchoring to
`get_read_block_error` means every future addition there protects this path too.
The narrow checks are kept as a fallback since they also cover dirs that guard
does not (`.aws`, `.gnupg`, `.kube`, etc.), and the canonical lookup is wrapped
so it can never crash reference expansion.

N/A

- [x] 🔒 Security fix

- `agent/context_references.py`: `_ensure_reference_path_allowed` now also
  consults `agent.file_safety.get_read_block_error` after its existing checks
  and refuses the reference when that canonical guard flags the resolved path.
  The lookup is wrapped so guard-resolution failures fall back to the explicit
  checks instead of breaking expansion.
- `tests/agent/test_context_references.py`: added
  `test_blocks_canonical_read_denylist_credential_stores`, asserting that
  `@file` attaches for `auth.json`, `.anthropic_oauth.json`, `mcp-tokens/*`, and
  a project-local `.env` are all refused and their secret bodies never reach the
  expanded message.
- `scripts/release.py`: added the contributor email to `AUTHOR_MAP` (release
  gate).

1. `scripts/run_tests.sh tests/agent/test_context_references.py` — all 15 tests
   pass, including the new credential-store case.
2. Regression proof: stash `agent/context_references.py`, run the suite with
   `-- -k canonical`, and confirm the new test fails (secrets leak into the
   message) without the fix; restore and confirm it passes.
3. `ruff check agent/context_references.py tests/agent/test_context_references.py`
   and `python scripts/check-windows-footguns.py agent/context_references.py
   tests/agent/test_context_references.py` both pass.

- [x] I've read the Contributing Guide
- [x] My commit messages follow Conventional Commits (`fix(scope):`, etc.)
- [x] I searched for existing PRs to make sure this isn't a duplicate
- [x] My PR contains **only** changes related to this fix (plus the AUTHOR_MAP release gate)
- [x] I've run the test suite for the touched area and all tests pass
- [x] I've added tests for my changes (required for bug fixes)
- [x] I've tested on my platform: macOS 15 (Darwin 25.5)

- [x] I've updated relevant documentation (README, `docs/`, docstrings) — or N/A
- [x] I've updated `cli-config.yaml.example` if I added/changed config keys — or N/A
- [x] I've updated `CONTRIBUTING.md` or `AGENTS.md` if I changed architecture or workflows — or N/A
- [x] I've considered cross-platform impact (Windows, macOS) — or N/A
- [x] I've updated tool descriptions/schemas if I changed tool behavior — or N/A
2026-07-01 02:43:49 -07:00
kshitijk4poor 8f1d22d7ed chore(release): map r266-tech contributor noreply email for #55780 salvage 2026-07-01 15:01:33 +05:30
teknium1 116a63d3a0 chore(release): map jcjc81 + Tranquil-Flow in AUTHOR_MAP for #54947 cluster salvage 2026-07-01 02:29:24 -07:00
Teknium 522a5e93b2 chore(release): map x9x9x9x9x9x91 for #49247 salvage 2026-07-01 02:18:56 -07:00
itenev f981d47cb0 fix(gateway): prevent Discord disconnects from blocking event loop
models_dev.py's fetch uses a synchronous requests.get(timeout=15). Called
from the async gateway message handlers, it blocked the event loop for up
to 15s, starving Discord heartbeats and causing ClientConnectionResetError
disconnects.

Adds get_model_context_length_async() which offloads the entire sync
resolution chain to a worker thread via asyncio.to_thread(), and switches
the two async gateway call sites (_prepare_inbound_message_text,
_handle_message_with_agent) to await it. The loop stays responsive; the
sync path remains the single source of truth for the cache.

Salvaged from PR #22753 by @itenev. Follow-up: dropped the unused
fetch_models_dev_async/lookup_models_dev_context_async aiohttp variants
from the original PR (dead code with zero callers that had drifted from
the sync cache logic) — the to_thread wrapper already runs the sync path
off-loop, so they were redundant.
2026-07-01 02:17:35 -07:00
Teknium ea533e7f41 chore(release): map justin-cyhuang contributor email for #31960 salvage 2026-07-01 02:12:25 -07:00
Teknium 259e6b87a7 fix(teams-pipeline): reject dot-only recording display_name
Path(raw).name reduces '..'/'.'/'' to themselves, so basename
extraction alone still let a Graph-provided display_name of '..' or
'../' escape the temp recording directory (tmp_dir / '..' resolves to
the parent). Reject the dot-only basenames explicitly and fall back to
the artifact id. Extends @outsourc-e's regression coverage with the
dot-only cases.
2026-07-01 02:03:48 -07:00
teknium1 6d30f8c0ab chore: add AUTHOR_MAP entry for PR #52534 salvage (@qWaitCrypto) 2026-07-01 02:03:40 -07:00
teknium1 49cb06c07a chore(release): map sasquatch9818 for PR #41198 salvage 2026-07-01 01:54:45 -07:00
kshitijk4poor 58ea7f9071 chore(release): map claudlos contributor email for #52351 salvage 2026-07-01 14:23:01 +05:30
Teknium f70abae606 chore(release): map kernel-t1 for .env sanitizer salvage (#41349) 2026-07-01 01:50:32 -07:00
teknium1 db2ac840c1 chore(release): map kyzcreig@gmail.com in AUTHOR_MAP 2026-07-01 01:44:40 -07:00
kshitijk4poor 843a3be7d6 chore(attribution): map baris@writeme.com -> isair for salvaged #50124 2026-07-01 14:09:15 +05:30
Teknium a56bfeb2cb chore(release): map approval-bypass PR contributors
AUTHOR_MAP entries for the salvaged shell-bypass fixes:
xy200303 (#40663), YLChen-007 (#26965), egilewski (co-author #40663).
necoweb3 (#55653) already mapped.
2026-07-01 01:39:10 -07:00
teknium1 907cbba885 chore(release): add Vesna-9 to AUTHOR_MAP for #41274 salvage 2026-07-01 01:38:59 -07:00
teknium1 5e64dd9a98 chore: map charleneleong84 email to AUTHOR_MAP for #11736 salvage 2026-07-01 01:36:34 -07:00
Teknium 12556a9a77
chore(scripts): drop Open WebUI local bootstrap script (#56178)
Remove scripts/setup_open_webui.sh and its 'one-command local bootstrap'
doc sections (EN + zh-Hans). The script pip-installed the third-party Open
WebUI frontend into ~/.local and managed a launchd/systemd user service —
a maintenance liability for downstream software we don't own, and the source
of the LAN first-admin signup footgun in #36121.

The Open WebUI *integration* via the OpenAI-compatible API server is
unaffected: the Docker/Docker-Compose setup, multi-user profile guide, and
troubleshooting in open-webui.md stay, and Open WebUI remains a listed
supported frontend. Only the install-and-service bootstrapper is gone.
2026-07-01 01:30:40 -07:00
teknium1 80d0ff8da5 chore: add AUTHOR_MAP entry for PR #40978 salvage (@friendshipisover) 2026-07-01 01:27:26 -07:00
ryo-solo d578b6165d fix(api_server): pop fallback model kwarg to prevent AIAgent collision
When the primary provider's auth fails (expired token / 429 quota cap),
_resolve_runtime_agent_kwargs() falls through to the fallback provider
chain, whose runtime dict carries its own 'model' key. api_server's
_create_agent then did AIAgent(model=model, **runtime_kwargs), colliding
on 'model' and 500ing every /v1/chat/completions request while a fallback
was active. Pop the runtime model and let it override the config model,
mirroring the native gateway path (_resolve_session_agent_runtime).

Salvaged from #35716 by @ryo-solo (earliest submitter); the PR's second
half (Mistral reasoning_content strip) is already handled on main and
dropped.

Co-authored-by: Hermes Agent <noreply@nousresearch.com>
2026-07-01 01:26:27 -07:00
teknium1 ce9d180a94 chore: add redactdeveloper to AUTHOR_MAP for PR #36897 salvage 2026-07-01 01:25:43 -07:00
teknium1 081c91c147 chore: add AUTHOR_MAP entry for PR #40773 salvage (rrevenanttt) 2026-07-01 01:25:24 -07:00
petrichor-op f2a528fb59 fix(agent): never persist empty-response recovery scaffolding
Ephemeral empty-response/prefill recovery scaffolding (the synthetic
assistant "(empty)" turn, the user nudge, the terminal "(empty)"
sentinel, and the thinking-only prefill placeholder) exists only to
drive the next API retry; the in-memory loop pops it before appending
the real response. The append-only flush did not mirror that, so a
mid-turn persist could commit scaffolding to the SQLite session store
(and JSON log), and a resumed session would replay synthetic
"(empty)"/nudge turns as genuine context — re-poisoning the empty-retry
boundary forever.

Filter ephemeral scaffolding at both durable-write sites
(_flush_messages_to_session_db + _save_session_log), by flag not
position, so buried scaffolding (an answered nudge leaves the synthetic
pair mid-list) is skipped too. Covers all three flags including
_thinking_prefill.

Adapted onto current main's identity-tracking flush.

Cherry-picked from #41281 by petrichor-op.
2026-07-01 01:08:27 -07:00
teknium1 cf427ccf08 chore: add AUTHOR_MAP entry for PR #35130 salvage (@jnibarger01) 2026-07-01 01:05:28 -07:00
teknium1 deb4629764 chore: add AUTHOR_MAP entry for PR #30491 salvage (MattKotsenas) 2026-07-01 01:02:23 -07:00
teknium1 7136b5382a chore: add JustinOhms to release AUTHOR_MAP for PR #24469 salvage 2026-07-01 00:45:31 -07:00
teknium1 3aebdb1d23 chore: add AUTHOR_MAP entry for PR #22523 salvage (@H2KFORGIVEN) 2026-07-01 00:27:09 -07:00
Teknium 8d78be5460
revert: back out prompt_caching.enabled toggle (#56105) for re-evaluation (#56126)
* Revert "fix(caching): honor prompt_caching.enabled across model switch + fallback"

This reverts commit 36f9f50145.

* Revert "fix: allow disabling prompt caching"

This reverts commit c1c1a12fe6.
2026-07-01 00:20:32 -07:00
teknium1 36f9f50145 fix(caching): honor prompt_caching.enabled across model switch + fallback
@janrenz's PR #35862 added prompt_caching.enabled=false at init only. But
_anthropic_prompt_cache_policy re-derives _use_prompt_caching on every /model
switch (agent_runtime_helpers) and fallback-model swap (chat_completion_helpers),
which re-enabled markers and re-broke the strict proxy the toggle was meant to fix.

Move the kill switch into anthropic_prompt_cache_policy so it returns (False, False)
on every path. Drop the now-redundant init-time override (kept @janrenz's isinstance
hardening on the cache_ttl read). Add policy-level tests + docs for the toggle.

Follow-up to salvaged PR #35862.
2026-07-01 00:10:42 -07:00
syahidfrd 0198713c33 fix(security): reuse auth chain when tagging unverified senders in Slack threads
Mitigates indirect prompt injection (CWE-863) in Slack thread context.
When the bot is mentioned mid-thread for the first time, _fetch_thread_context
pulls the full thread via conversations.replies and prepends every reply to
the LLM prompt. Replies from senders not on the allowlist were rendered
identically to authorised senders, letting a third party in a shared channel
inject instructions the model might act on when answering the next authorised
message.

- BasePlatformAdapter.set_authorization_check / _is_sender_authorized, registered
  by GatewayRunner._make_adapter_auth_check() with a closure over the existing
  _is_user_authorized chain (platform/global/group allowlists, allow-all flags,
  pairing store all stay the single source of truth — no env-var re-parsing).
- Tags non-bot thread messages whose sender fails the auth check with an
  [unverified] prefix; strengthens the header with soft guidance only when at
  least one unverified message is present, so setups without an allowlist see
  no behaviour change.
- Wired into all three adapter-init sites in run.py (start, reconnect watcher,
  restart) so the reconnect path is covered too.

Softened wording: adapted from the original [untrusted] tag to [unverified]
and non-accusatory header framing — the label reflects allowlist status, not
a judgment about the person. Adapter relocated to plugins/platforms/slack/
since the PR was authored.

Salvaged from #17059.
2026-06-30 18:05:43 -07:00
teknium1 7cb85733b8 chore(release): add AUTHOR_MAP entries for #54609, #54912 salvage 2026-06-30 17:45:45 -07:00
teknium1 698c287fd0 chore(release): add AUTHOR_MAP entry for CRWuTJ (PR #17082 salvage) 2026-06-30 17:39:30 -07:00
teknium1 7de485703b fix(gateway): preserve media + reply payload when /queue defers a turn
/queue rebuilt the queued MessageEvent with only text/type/source/
message_id/channel_prompt, silently dropping any photo, document, voice,
or reply context attached to the command. The deferred turn then ran with
the attachment lost. Carry the full payload through, and accept a /queue
that has media but no prompt text (e.g. "/queue" as an image caption).

Salvaged from #13913 by @ypwcharles — the gateway busy-session/queue
infrastructure was rewritten since that PR (Telegram moved to
plugins/platforms/, /queue now uses the FIFO chain), so the media fix is
reimplemented against the current handler; the PR's batching and
busy-bypass changes targeted code paths that no longer exist.

Co-authored-by: ypwcharles <92324143+ypwcharles@users.noreply.github.com>
2026-06-30 17:32:35 -07:00
Scott Gabel 4a7a6fd401 fix(approval): redact secrets in user-facing approval prompts
The dangerous-command approval prompt renders the flagged command so the
user can decide whether to approve. If the agent constructed it with a
credential (curl -H 'Authorization: Bearer sk-...', psql postgres://user:pw@host,
an execute_code script with api_key = 'sk-...'), that secret hit stdout and,
via the gateway notify payload, Discord/Slack messages — which are
screenshottable and forwardable.

Apply the existing agent.redact.redact_sensitive_text() to every user-facing
approval surface. Redaction is display-only: the raw command still executes
after approval, and approval persistence keys off pattern_key (not the command
text), so the allowlist is unaffected. Decision context (URL, flags, command
structure) is preserved; only the secret value masks.

Covers all surfaces, including the execute_code path the original PR missed:
- prompt_dangerous_approval(): callback + stdout fallback
- check_all_command_guards(): gateway approval_data + cron/batch pending fallback
- check_execute_code_guard(): gateway approval_data + no-notifier pending fallback
  (script body can embed credentials)

Adds TestApprovalPromptRedaction covering callback redaction, no-over-redaction
of clean commands, and the execute_code pending fallback.

Salvaged from PR #13139 by @sgabel; extended to the execute_code surface.
2026-06-30 17:29:11 -07:00
teknium1 caa2034f88 chore(release): map codexGW noreply email for PR #12302 salvage 2026-06-30 16:38:31 -07:00
teknium1 638d2e7bfc fix(memory/holographic): apply FTS5 sanitizer to search_facts sibling
The store-level search_facts() shared the same raw-MATCH bug class as
_fts_candidates (FTS5 AND-joins tokens, zeroing prose recall). Route it
through FactRetriever._sanitize_fts_query via a lazy import to keep the
store->retrieval layering acyclic. Also add cyb3rwr3n to release AUTHOR_MAP.
2026-06-30 15:55:11 -07:00
teknium1 86200e7583 chore(release): map kyssta-exe id-prefixed noreply email for PR #55657 salvage 2026-06-30 15:49:36 -07:00
teknium1 0b3752eede chore(release): add AUTHOR_MAP entry for lEWFkRAD (#53848 salvage) 2026-06-30 12:07:01 -07:00
xxxigm a40f22798e fix(installer): reset managed clone when ff-only pull fails
Bootstrap and desktop updates run install.ps1/install.sh, which aborted
with exit 128 when the managed checkout had diverged from origin/main.
Mirror the hermes update recovery path: reset to origin/$BRANCH instead
of failing the repository stage.
2026-06-30 20:11:01 +07:00
Neo c969090878 fix(cli): clear input-blocking overlays when interrupting a running agent
Interrupting the agent while an approval/clarify/sudo/secret prompt is up
left the overlay state dict set with no thread servicing it. The prompt's
worker thread is torn down on interrupt, but read_only (gated on
_command_running) plus the keypress filter kept the CLI input locked until
the prompt's own timeout expired — the terminal appeared frozen.

Drain and clear all four input-blocking overlays on interrupt via a single
helper (_clear_active_overlays_for_interrupt): approval -> deny,
clarify/sudo/secret -> cancel, each guarded so a dead queue can't block the
others; sudo restores the pre-modal draft. Wired into all three interrupt
paths — new-message interrupt, Ctrl+C, and Ctrl+Q. Blocking overlays now
clear AND fall through so one keypress both clears a stale overlay and
interrupts a still-running agent; the /model picker and slash-confirm
foreground prompts keep their cancel-and-return behavior.

Closes #13618.
2026-06-30 04:49:29 -07:00
teknium1 8e6fd4cfa6 chore(release): add AUTHOR_MAP entry for londo161 (#15795 salvage) 2026-06-30 04:38:43 -07:00
teknium1 6148a9a3fe chore(release): map nnnet author email for PR #25142 salvage 2026-06-30 04:23:03 -07:00
fayenix d6c53dcdcb fix(gateway): stop per-turn agent-cache eviction from model + message_id signature churn
Two independent bugs evicted the cached gateway AIAgent on every turn,
preventing the prompt cache from ever warming:

1. Model normalization mismatch: the post-run fallback-eviction check
   compared _agent.model (stripped in AIAgent.__init__) against the raw
   _resolve_gateway_model() config string. For vendor-prefixed config on
   native providers (e.g. 'deepseek/deepseek-v4-pro' vs 'deepseek-v4-pro')
   this was always unequal, so the agent was evicted after every
   successful run. Normalize _cfg_model the same way (skip aggregators).

2. Discord triggering message_id leaked into the cached system prompt via
   build_session_context_prompt()'s Discord IDs block. message_id changes
   every turn, so the agent-cache signature (computed from the ephemeral
   prompt) changed every Discord turn -> rebuild every message. The id is
   now injected per-turn into the user message (where per-turn content
   belongs and does not touch the cache signature); the cached IDs block
   carries a static pointer to it, preserving reply/react/pin via the
   discord tools.

Adapted from #28846. Bug #1 fix is the contributor's; bug #2 reworked to
be non-destructive (keeps the triggering-id capability instead of deleting
it). Redundant auto-reset eviction (already on main via #9893/#48031) and
the wrong-premise reset_context_note plumbing from the original PR were
dropped.

Co-authored-by: Hermes Agent <hermes@nousresearch.com>
2026-06-30 04:22:41 -07:00
Zane Ding ac380050ea fix(credential-pool): distinguish OpenRouter upstream 429s from account 429s
OpenRouter returns 429 in two shapes: an account-level throttle on the
user's key, and an upstream-provider throttle (DeepSeek/Anthropic/etc.
rate-limiting OpenRouter's aggregate traffic). The classifier treated
both identically and rotated/exhausted OPENROUTER_API_KEY on every 429 —
burning the key for ~24min and silently disabling auxiliary features
(compression, summarization, vision) on an upstream throttle where the
key was healthy.

Add a FailoverReason.upstream_rate_limit classified from OpenRouter's
unambiguous wrapper message "Provider returned error" (the same signal
the metadata-raw parser already trusts). Recovery skips credential
rotation and defers to the fallback chain to switch models instead.

Co-authored-by: Hermes Agent <127238744+teknium1@users.noreply.github.com>
2026-06-30 03:57:14 -07:00
Teknium abca77615a chore(release): map Jeffgithub0029 author email for #28558 salvage 2026-06-30 03:51:08 -07:00