Commit Graph

1011 Commits (af7dceaf77bbcd5dcafe4f65982c7cd7df5f4c4a)

Author SHA1 Message Date
Teknium f1e6d39a74
feat(computer_use): disable cua-driver telemetry by default, add opt-in (#50842)
* feat(computer_use): disable cua-driver telemetry by default, add opt-in

cua-driver ships anonymous PostHog usage telemetry ENABLED by default
upstream (fires cua_driver_install / cua_driver_doctor events to
eu.i.posthog.com). Hermes now disables it for our users unless they
explicitly opt in.

- New config key `computer_use.cua_telemetry` (default false) in
  DEFAULT_CONFIG.
- `cua_backend.cua_driver_child_env()` injects
  `CUA_DRIVER_RS_TELEMETRY_ENABLED=0` into the child env when telemetry is
  disabled (the default); leaves the var untouched on opt-in so the driver
  uses its own default. Reads config fail-safe — any error defaults to
  telemetry off.
- Routed every cua-driver spawn site through the policy: MCP backend
  (StdioServerParameters env), `cua_driver_update_check`, doctor's
  health_report Popen, the install.sh/install.ps1 runner, and the
  `--version` / status probes.
- Docs: new Telemetry subsection in computer-use.md (EN).
- Tests: tests/computer_use/test_cua_telemetry.py — default disables,
  explicit-false disables, opt-in leaves var untouched, config-failure
  fails safe, inherited-enabled is overridden off.

Verified live on Linux against the real cua-driver-rs 0.6.0 binary: with
the var=0 the driver reports "telemetry: disabled via
CUA_DRIVER_RS_TELEMETRY_ENABLED" and sends no event; with it unset it logs
"sending event: cua_driver_doctor". 213 computer_use + install tests green.

* fix(dashboard): fold computer_use config category into agent tab

The new computer_use.cua_telemetry key created a single-field dashboard
config category, tripping test_no_single_field_categories (web_server's
invariant that categories with <2 fields must be merged to avoid tab
sprawl). Add computer_use -> agent to _CATEGORY_MERGE, matching the
existing onboarding/telegram single-field folds.
2026-06-22 09:57:16 -07:00
Francesco Bonacci f2e37549c6 feat(computer_use): cross-platform cua-driver (macOS/Windows/Linux)
Make the computer_use toolset platform-agnostic by driving cua-driver on
macOS, Windows, and Linux. Consumes the 8 cua-driver decoupling surfaces
(capability discovery, structuredContent AX tree, opaque element_token,
click button enum, explicit mimeType, machine-readable manifest,
structured list_windows, structured health_report), each degrading
gracefully on older drivers.

Adds `hermes computer-use doctor` (drives cua-driver health_report with a
per-OS check matrix and an exit 0/1/2 ok/degraded/blocked contract), full
typed wrappers for the previously-uncovered cua-driver tools plus a generic
call_tool escape hatch, per-session agent-cursor lifecycle, platform-aware
system-prompt guidance (host-deterministic, cache-safe), and honors
HERMES_CUA_DRIVER_CMD end-to-end.

Replaces the macOS-only skills/apple/macos-computer-use skill with a
cross-platform skills/computer-use skill, and refreshes the EN + zh-Hans
docs.

Supersedes #44221 (Windows-enablement salvage of #30660).

Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
2026-06-22 06:42:30 -07:00
Teknium ff85af3fc7
feat(goals): /goal wait <pid> — park the loop on a background process (#50503)
* feat(goals): add /goal wait <pid> barrier to park the loop on a background process

The /goal loop re-pokes the agent every turn via the post-turn judge. When a
goal is gated on a long-running background process (CI poller, build, test
matrix, deploy) that produces nothing to judge yet, this spins the agent into
'is it done?' busy-work and burns the turn budget.

/goal wait <pid> [reason] parks the loop: while the PID is alive, the judge is
skipped, no turn is consumed, no continuation fires, and /goal status shows a
parked indicator. The barrier auto-clears the moment the process exits (the
agent's notify_on_complete watcher is the natural wake signal), then the next
turn resumes normal judging. /goal unwait clears it manually; pause/resume/clear
drop it; a dead/stale PID can never wedge the loop.

Wired across CLI, gateway, and the mid-run command guard for parity. Barrier
persists in SessionDB.state_meta (survives /resume); GoalState gains
backward-compatible waiting_on_pid/waiting_reason/waiting_since fields. 12 new
tests; docs updated.

* fix(goals): use gateway.status._pid_exists for liveness, not os.kill(pid,0)

The Windows-footguns CI guard flagged os.kill(pid, 0) in _pid_alive — on
Windows that's not a no-op, it routes to CTRL_C_EVENT and hard-kills the
target's console process group (bpo-14484). Delegate to the canonical
footgun-safe gateway.status._pid_exists (psutil + ctypes/POSIX fallback)
instead, with a direct-psutil last resort.

* feat(goals): judge-driven auto-wait — the loop parks itself, no manual /goal wait

Makes the wait barrier automatic. Every turn the judge is shown the agent's
live background processes (pid, command, uptime, output tail from the
process_registry) alongside the goal + response, and can return a new 'wait'
verdict instead of continue:
  {"verdict":"wait","wait_on_pid":N}      → park until that process exits
  {"verdict":"wait","wait_for_seconds":N} → park until the deadline passes
evaluate_after_turn acts on the directive (sets the barrier, parks the loop)
so the agent isn't re-poked into busy-work while CI/builds/deploys run. Adds a
time-based waiting_until barrier alongside the pid barrier; both auto-clear and
can never wedge the loop. Drivers (CLI, gateway, tui_gateway) feed the live
registry in via gather_background_processes(). Manual /goal wait stays as an
override. Judge verdict contract widened to (verdict, reason, parse_failed,
wait_directive); legacy {"done":bool} shape still accepted.

* test(goals): update kanban _fake_judge to the 4-tuple judge contract

CI test(3) caught it: test_kanban_goal_mode's _fake_judge still returned the
3-tuple (verdict, reason, parse_failed), but the kanban loop now unpacks the
4-tuple (+ wait_directive). Update the fake to return None for the directive
and accept the background_processes kwarg.

* feat(goals): trigger-based wait — park on a process's own signal, not just exit

Addresses two gaps in the judge-driven wait: (1) the judge could only express
'wait until PID exits' or 'wait N seconds', so a long-lived watcher/server that
fires a trigger MID-RUN (and may never exit) couldn't be waited on; (2) the
process's own watch_patterns/notify_on_complete trigger was invisible to the judge.

Adds a session-based barrier (waiting_on_session) that releases on the process's
OWN trigger via process_registry.is_session_waiting(): the session exits, OR (if
started with watch_patterns) its pattern matches — even while the process keeps
running. list_sessions() now surfaces session_id + watch_patterns/watch_hit/
notify_on_complete so the judge sees the trigger and is told to prefer
wait_on_session for trigger processes. Judge verdict gains a {wait_on_session}
directive (preferred over pid). Backward-compatible GoalState field; pid + time
barriers unchanged.

Tests: TestSessionTriggerBarrier (release on mid-run pattern match while alive,
release on exit, unknown-session, full park→trigger→resume, parse, validation,
backcompat load). 105 goal-surface + 85 process_registry tests green.
2026-06-22 06:27:29 -07:00
Kartik 2e779d11a0
feat(mem0): v3 API, OSS mode, update/delete tools, telemetry & review fixes (#15624)
* fix: update to version 3 endpoints and adding update and delete tool

* chore: removing the test md file

* fix: prevent circuit breaker on client errors in Mem0 provider

* chore: add telemetry for platform version

* feat: add OSS mode support to Mem0 memory provider

* chore: bump mem0ai dependency to >=2.0.1 in memory plugin

* refactor: enhance dependency checks and embedder config in mem0 backend

* refactor: adjust fact storage message for OSS mode

* refactor: expand user paths, add collection recreation on dimension change for Qdrant

* fix(mem0): make MEM0_USER_ID override gateway-native ids and tag writes with channel

When MEM0_USER_ID was configured (env or mem0.json), the gateway-native id
from kwargs (Telegram numeric id, Discord snowflake, ...) still won, so the
same human ended up under different user_ids per channel and memories never
merged across CLI / Telegram / Slack / Discord. Mirrors openclaw's cfg.userId
pattern: configured override wins, gateway-native id is the fallback.

The legacy "hermes-user" placeholder default written by the setup wizard is
treated as unset to avoid silently bucketing every gateway user together.

Also tag every write with metadata.channel (cli/telegram/discord/...) so the
dashboard can offer per-channel filtered views without coupling identity to
the channel; document the read/write filter asymmetry as intentional
(reads scope to user_id only for cross-agent recall).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: improve Mem0 memory provider backend, pagination, config, and error handling

* refactor: update mem0 telemetry code, docs, and bump version

* fix(mem0): make get_config_schema() return unified schema with mode-aware required flag

Schema always includes api_key field so picker shows "API key / local" for
both modes. In OSS mode api_key.required=False so status won't mislead.

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

* refactor: improve mem0 telemetry, add env var key and OSS mode detection

* chore: bump mem0ai lower bound to 2.0.4 (latest SDK release)

* refactor: set telemetry sample rate to 1.0 and update docs for opt‑out

* fix(mem0): resolve 15 correctness, thread-safety, and resource bugs

Thread safety:
- Protect circuit breaker counters with _breaker_lock (race between
  prefetch/sync daemon threads and main thread)
- Wrap sync_turn thread creation in _sync_lock; skip if previous sync
  is still alive after 5 s join to prevent duplicate memory ingestion
- Guard _schedule_flush timer creation under _queue_lock (TOCTOU race)
- Capture local `backend` reference in prefetch/sync closures so
  shutdown() nulling self._backend cannot crash in-flight threads

Correctness:
- Fix bool("false")==True for rerank param; parse string values explicitly
- Guard page/top_k with max(1,...) and move int() inside try blocks
- Fix fact_count=0 always in OSS mode (Memory.add returns list, not dict)
- Fix prefetch() not clearing result when thread still alive after timeout
- Fix atexit.register accumulating on repeated initialize() calls

Backend / setup:
- Handle Qdrant named-vector collections in _recreate_collection_if_dims_changed
  (vectors is a dict; .size access raised AttributeError, swallowed silently)
- Wrap QdrantClient and psycopg2 conn/cursor in try/finally to prevent leaks
- Resolve ollama_bin at top of _ensure_ollama; use it for ollama pull
- Fix embedder key lookup when LLM provider has no env_var (e.g. ollama)

Also: remove _telemetry_enabled cache (env var check is cheap), bump
required mem0ai to >=2.0.7, minor README wording fix.

* fix(mem0): fix brittle qdrant path test + add telemetry sample-rate docs

- Replace generator-throw lambda with a proper def in
  test_qdrant_path_not_writable; use tmp_path instead of a hardcoded
  /nonexistent path so the test is root-safe
- Add MEM0_TELEMETRY_SAMPLE_RATE to memory-providers.md (was only
  in the plugin README, not the user-guide docs)

* revert: remove MEM0_TELEMETRY_SAMPLE_RATE from user-guide docs

* refactor: remove telemetry from mem0 plugin and update documentation

* fix(mem0): set stdin=DEVNULL on setup subprocess calls

The TUI stdin guard (scripts/check_subprocess_stdin.py) requires every
subprocess call in plugin code to set stdin= so it can't inherit the
gateway's JSON-RPC stdin fd. Muzzle the docker/ollama calls in the OSS
setup wizard with stdin=subprocess.DEVNULL (none need interactive input).
Also covers the docker-inspect call the linter's regex misses.

---------

Co-authored-by: chaithanyak42 <chaithanya.kumar42a@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-22 12:30:47 +00:00
Eugeniusz Gilewski 8845f3316c fix(security): restrict dashboard plugin backend import to bundled plugins (#43719)
Defense-in-depth for the dashboard plugin auto-import path. The web server
auto-imports and mounts the Python backend (dashboard/manifest.json -> api file)
of plugins found in ~/.hermes/plugins/ (user) and ./.hermes/plugins/ (project),
not just bundled plugins. So any plugin that reaches one of those dirs gets
arbitrary Python executed on the next dashboard start.

NOTE ON THREAT MODEL: #43719's originally-documented delivery chain (a public
--insecure dashboard + open API used to git clone a malicious repo into
~/.hermes/plugins/) is ALREADY mitigated on main — since the June 2026
hermes-0day hardening, a non-loopback bind ALWAYS requires an auth provider and
--insecure no longer bypasses the auth gate. This change is therefore NOT
closing that (now-authenticated) network path; it removes the residual
'arbitrary code executes merely because a plugin is on disk' hazard, which still
applies when a plugin arrives by other means: a socially-engineered git clone,
a supply-chain drop, an authenticated-but-malicious actor, or a future
regression in the auth gate. Untrusted on-disk code should not auto-execute.

Restrict dashboard backend Python auto-import to BUNDLED plugins only. User and
project plugins may still extend the dashboard UI via static JS/CSS, but their
api Python file is never auto-imported. Two layers: _discover_dashboard_plugins
scrubs api/_api_file for user/project sources (and bundled wins name conflicts
so a non-bundled plugin cannot shadow a trusted backend route);
_mount_plugin_api_routes re-refuses user/project at mount time. Tightens the
prior GHSA-5qr3-c538-wm9j / #29156 hardening (bundled+user) to bundled-only.

Salvaged from #44472 (@egilewski) onto current main.
2026-06-22 17:51:37 +05:30
Brooklyn Nicholson 5342eccf12 Merge remote-tracking branch 'origin/main' into bb/pets 2026-06-22 05:25:49 -05:00
Shannon Sands 2455e1801b Make email pairing opt-in 2026-06-21 22:43:57 -07:00
teknium1 4314d451ca fix(gateway): accept any inbound file type across all messaging platforms
Authorization to message the agent is the gate, not the file extension.
Previously the inbound-attachment allowlist (SUPPORTED_DOCUMENT_TYPES) was
opt-OUT on Discord (allow_any_attachment defaulted false) and had no bypass
at all on Telegram/Slack — so an .html (or any non-allowlisted type) was
dropped or hard-rejected before the agent saw it.

Now every authorized upload is cached and surfaced to the agent regardless
of type:
- base.cache_media_bytes(): unknown types cache as octet-stream (or the
  caller-supplied MIME) instead of returning None — fixes the chokepoint
  that Teams/Telegram-media route through.
- discord/telegram/slack adapters: removed the allowlist reject/skip; any
  non-media attachment is typed DOCUMENT and cached. Known types keep their
  precise MIME.
- Text inlining now gates on a shared _TEXT_INJECT_EXTENSIONS set (text +
  code + config + markup) instead of a blind UTF-8 decode, so binary formats
  (PDF/zip/docx) with ASCII headers are never inlined.
- gateway/run.py emits the path-pointing context note for every DOCUMENT,
  including non text/application MIME types.
- discord.allow_any_attachment is now a documented no-op kept for config
  back-compat.

Validation: 357 gateway tests pass; E2E confirms .html/.bin/custom types
cache, known types stay precise, PDFs are not inlined.
2026-06-21 22:43:45 -07:00
Teknium e448b21414
feat(dashboard): interactive auth setup on no-provider non-loopback bind (#50551)
When `hermes dashboard --host 0.0.0.0` is run interactively with the auth
gate engaged but no DashboardAuthProvider configured, prompt to set up the
bundled username/password provider on the spot (or point at `hermes dashboard
register` for OAuth) instead of only emitting the fail-closed error.

- main.py: `_maybe_setup_dashboard_auth_interactively()` runs before
  start_server. No-ops on loopback binds, when a provider is already
  registered, or when stdin/stdout isn't a TTY (Docker/s6, CI, piped runs) so
  the fail-closed SystemExit stays the backstop for unattended deploys. On the
  password path it writes dashboard.basic_auth.{username,password_hash,secret}
  to config.yaml (scrypt hash, never plaintext), then force-rediscovers
  plugins so the basic provider registers before the gate check.
- web_server.py: fix the fail-closed hint — it told operators to set
  `dashboard_auth.basic.username` but the provider reads `dashboard.basic_auth`.
- docs: note the interactive setup under Fail-closed semantics.

No new env vars; reuses the existing dashboard.basic_auth config surface.
2026-06-21 20:21:48 -07:00
Teknium 7130d60861
feat(providers): remove google-gemini-cli + google-antigravity OAuth providers (#50492)
* feat(providers): remove google-gemini-cli + google-antigravity OAuth providers

Google now actively bans accounts for third-party tools that piggyback on
Gemini CLI / Antigravity / Code Assist OAuth, and because abuse prevention
sits at a backend layer the ban can extend to the entire Google account
(Gmail/Drive), with a second violation being permanent.
Ref: https://github.com/google-gemini/gemini-cli/discussions/20632

Removes both OAuth inference providers entirely (modules, provider profiles,
auth/runtime/config/models wiring, the /gquota Code Assist quota command,
the antigravity-cli optional skill, desktop + docs surface in en + zh-Hans).
The API-key 'gemini' provider (GOOGLE_API_KEY/GEMINI_API_KEY against
generativelanguage.googleapis.com) is unaffected and stays fully supported.

* fix(skills): keep the antigravity-cli skill — only the OAuth provider is removed

The antigravity-cli optional skill orchestrates the external `agy` binary as
a coding-agent tool via the terminal tool — it does NOT wrap Hermes inference
through the banned google-antigravity OAuth provider, so it carries none of
the account-ban risk that motivated removing that provider. Restore the skill,
its docs page, the sidebar entry, and the optional-skills catalog row. The
google-antigravity / google-gemini-cli inference providers stay fully removed.
2026-06-21 19:53:27 -07:00
teknium1 eb51c180e6 fix(docker): replace dashboard --insecure with basic-auth provider
The s6 dashboard entrypoint and docker integration tests relied on
HERMES_DASHBOARD_INSECURE=1 to bring up a 0.0.0.0 dashboard with no auth
provider. With --insecure now a no-op (auth gate mandatory on non-loopback
binds), that path fails closed.

- s6 dashboard/run: drop --insecure derivation; warn that the env is a no-op
  and point operators at HERMES_DASHBOARD_BASIC_AUTH_* / OAuth.
- docker tests: supervision tests now register the bundled basic password
  provider (HERMES_DASHBOARD_BASIC_AUTH_USERNAME/_PASSWORD) so the gate has a
  provider and the dashboard binds. Rewrote the insecure-opt-out test to
  assert fail-closed (dashboard does NOT serve) instead of gate-bypass.
- docs (en + zh-Hans): HERMES_DASHBOARD_INSECURE documented as deprecated
  no-op; basic-auth is the zero-infra way to authenticate a containerized
  public dashboard.
2026-06-21 19:05:27 -07:00
Teknium 84e1d31e54
refactor(kanban): fold worker/orchestrator skills into injected guidance (#50473)
The kanban-worker and kanban-orchestrator bundled skills existed only to
be force-loaded into dispatcher-spawned workers, gated by
environments:[kanban] so they wouldn't leak into normal CLI listings.
That gating was fragile (the leak that #50443 patched) and the
--skills auto-load was already best-effort — most workers ran without it
because the bundled skill isn't present in profile-scoped skills dirs.

Remove the skills entirely and promote their load-bearing content
(workspace kinds, deliverable artifacts, created-card integrity, profile
discovery) into KANBAN_GUIDANCE, which is already injected into every
kanban worker's system prompt. Net result: every worker reliably gets
the guidance, nothing can leak into a CLI/blank-slate session, and the
gating machinery is gone.

- agent/prompt_builder.py: promote the 4 load-bearing rules into KANBAN_GUIDANCE
- hermes_cli/kanban_db.py: drop --skills kanban-worker auto-injection + _kanban_worker_skill_available probe
- hermes_cli/kanban_swarm.py: drop skills=[kanban-orchestrator] on the root card
- hermes_cli/kanban.py: drop kanban-init skill seeding; fix help text
- delete skills/devops/kanban-{worker,orchestrator}
- docs: delete the two skill pages (EN+zh), fix sidebars/catalog/kanban.md/kanban-worker-lanes.md and the video-orchestrator + codex-lane references
- tests: update spawn-argv expectations; re-bound the guidance-size guard

Supersedes the skill-leak half of #50443 (credit @helix4u for flagging the area).
2026-06-21 17:06:48 -07:00
pmos69 8baa4e9976 feat(cli): add native Antigravity OAuth provider 2026-06-21 16:41:30 -07:00
Teknium b6d1072408
fix(cli): branch new worktrees from the fresh remote tip, not stale local HEAD (#50355)
hermes -w created the worktree branch from the standalone clone's HEAD, which
lags origin when the clone isn't freshly updated (it's only refreshed by
hermes update, not per session). Every worktree branch then rooted on a stale
base, so the PR diff GitHub computes against current main ballooned with
unrelated changes and the agent had to discover the staleness at push time and
rebase.

_resolve_worktree_base() now fetches and branches from the freshest available
ref: the current branch's upstream if it tracks one (so a deliberate
feature-branch worktree tracks its own remote), else the remote's default
branch (origin/HEAD), else local HEAD as a fail-soft fallback (offline / no
remote / detached). A bogus 'origin/(unknown)' default is guarded, and worktree
creation retries from HEAD if branching off the remote ref fails — so this is
never worse than the old behavior.

Gated by worktree_sync (default true); set worktree_sync: false to keep the
old branch-from-local-HEAD behavior. The resolved base is printed in the
session banner.

This is the follow-up to the #50319 session, where the standalone clone was
213 commits behind origin and the worktree inherited that stale base.
2026-06-21 12:42:11 -07:00
kn8-codes 6183e8ce1b fix(telegram): make Bot API 10.1 rich messages opt-in (default off)
Rich messages are not ready for primetime: current Telegram clients can
render Bot API 10.1 rich messages as blank/unsupported bubbles and make
them hard to copy as plain text, which is worse than the legacy
MarkdownV2 path for command snippets and mobile handoffs. Default the
rich_messages toggle to False so replies stay on the copyable legacy
path; users opt in per bot via platforms.telegram.extra.rich_messages:
true. Updates adapter, gateway config default, example config, English +
zh-Hans docs, and the default/opt-in tests.
2026-06-21 12:03:24 -07:00
Teknium 03563dabac
fix(gateway): raise session-hygiene hard message limit 400 → 5000 (#50194)
The gateway pre-compression hygiene valve force-compressed any session
crossing 400 messages regardless of token usage. On large-context (1M+)
models doing many short, message-dense turns, a healthy session at ~16%
token usage could hit 400 messages and get force-compressed — and the
compression summary's stale Active Task could then bleed into the next
turn.

The valve's actual purpose is to break a death spiral: when API calls
keep disconnecting on an oversized session, no token-usage data arrives,
the token threshold never fires, and the transcript grows unbounded.
It's a count-based floor for that pathological case only. 400 was tuned
for ~200K-context models and is far too low for modern large-context
sessions. Raise the default to 5000 — still well clear of any death
spiral, but no longer firing on legitimate long conversations.

The value remains fully configurable via compression.hygiene_hard_message_limit.
2026-06-21 08:26:19 -07:00
Tuna Dev 04730f32e7 fix(cli): warn when in-session model switch will preflight-compress
Adds hermes_cli/context_switch_guard.py mirroring the model_cost_guard
pattern. When a user switches models mid-session (Herm TUI picker, CLI,
or /model on Telegram/Discord), the warning surfaces on the existing
ModelSwitchResult.warning_message path used by the expensive-model
guard if the new model's compression threshold is below the current
session size.

Partial fix for #23767 — addresses only the 'user-facing guardrail
when switching from a high-context provider to a substantially
lower-context provider' slice. The other proposed fixes from that
issue (hard preflight token guard, metadata cache invalidation on
switch, compression safety invariant, oversized tool-output handling)
are out of scope for this PR.
2026-06-21 16:29:31 +05:30
loes5050 85f108ef03 test(cron): document consent-first self-learning suggestions 2026-06-20 23:23:47 -07:00
virtuadex defeda8c55 docs: sync documentation with current implementation 2026-06-20 23:23:47 -07:00
aieng-abdullah 74b5cc7ca4 docs(spotify): document 6-month re-auth cycle and add client-level invalid_grant test
- Remove the 'you only log in once per machine' claim from spotify.md
  and document the ~6-month refresh token expiry with re-auth instructions
- Add test_client_wraps_invalid_grant_as_spotify_auth_required_error to
  confirm SpotifyClient wraps AuthError(code=spotify_refresh_invalid_grant)
  into SpotifyAuthRequiredError with a user-facing message

Refs: #28155
2026-06-20 23:23:47 -07:00
Sworntech-dev fa53e36438 docs(hooks): document manual shell hook allowlisting 2026-06-20 23:23:47 -07:00
michael.chen 063155e234 docs(hooks): document subagent_start plugin hook 2026-06-20 23:23:47 -07:00
Sworntech-dev 38756f2d55 docs(docker): document gateway tool-loop hard stops 2026-06-20 23:23:47 -07:00
GauravPatil2515 cc30e0b659 docs(config): document auxiliary task fallback_chain 2026-06-20 23:23:47 -07:00
mintybasil 5d05415292 Expand .gitignore example 2026-06-20 20:42:49 -07:00
mintybasil 094d9cba6c Update docs to clarify requirement for gitignore 2026-06-20 20:42:49 -07:00
Railway9784 a9602d27e7 docs(skill): document context_length auto-detection resolution chain
When model.context_length is set in config.yaml, it blocks auto-detection
from the server's /v1/models endpoint. The skill incorrectly implied a
hard fallback to 131072. Add the resolution chain and the fix command
(hermes config set model.context_length "") to both the config table
and a new troubleshooting section.
2026-06-20 20:42:49 -07:00
yapsrubricsz0 abfbd618bd fix(docs): regenerate skill docs to fix stale cross-links, add tool-search to sidebar 2026-06-20 20:42:49 -07:00
Bartok9 31bdb60013 docs(skills): fix himalaya CLI arg order and download flag
Closes #48835

The bundled himalaya skill and its website docs documented command
syntax that does not match Himalaya CLI v1.2.0.

Verified against pimalaya/himalaya v1.2.0 source:
- message move: MessageMoveCommand declares target_folder BEFORE
  envelopes (src/email/message/command/move.rs) -> usage is
  '<TARGET> <ID>...', so 'move 42 "Archive"' is wrong; correct is
  'move "Archive" 42'.
- message copy: same ordering in copy.rs.
- attachment download: AttachmentDownloadCommand exposes the flag as
  '-d, --downloads-dir <PATH>' (src/email/message/attachment/command/
  download.rs), not '--dir'.

Fixed in all three surfaces that carried the wrong examples:
- skills/email/himalaya/SKILL.md
- website/docs/.../email-himalaya.md
- website/i18n/zh-Hans/.../email-himalaya.md
2026-06-20 20:42:49 -07:00
liuhao1024 4711936a3b fix(docs): remove non-existent conversation_entity setting from homeassistant troubleshooting 2026-06-20 20:42:49 -07:00
Brooklyn Nicholson 6fd839ac84 docs(pets): feature guide, petdex skill + catalog
Add the pets feature guide and the petdex skill (SKILL.md + bundled doc),
and register them in the website sidebar and skills catalog.
2026-06-20 14:18:43 -05:00
Teknium cf58f1a520
feat(titles): support language-aware title generation (#45296)
Make auxiliary title prompts match the user language by default, with an optional pinned `auxiliary.title_generation.language` config.
2026-06-19 17:15:52 -07:00
Teknium 26e76a75e5
feat(telegram): opt-in Online/Offline bot status indicator (#49134)
Sets the Telegram bot's short description (the line under its name) to
"Online" on gateway connect and "Offline" on clean disconnect, gated
behind extra.status_indicator (off by default).

Telegram bots have no presence/online dot — that's a user-account
feature the Bot API doesn't expose for bots. The short description is
the closest available surface, so this gives users a way to tell whether
the gateway is up from the bot's profile.

- New extra.status_indicator flag (+ status_online/status_offline text
  overrides), read in __init__ via config.extra — no config-schema change.
- _set_status_indicator() helper: best-effort, swallows API errors so it
  never blocks connect/disconnect; truncates to Telegram's 120-char cap.
- Wired Online after _mark_connected(), Offline at top of disconnect()
  while the bot HTTP client is still alive.
- 9 unit tests + Telegram docs section.

Requested by @ilTrumpista, cc @Teknium.
2026-06-19 11:38:39 -07:00
skyzh 9026a8c789 feat(gateway): add Raft bundled platform plugin with activity hooks
Adds a Raft platform adapter as a bundled plugin (plugins/platforms/raft/)
connecting Hermes to Raft as an external agent via a wake-channel bridge.
The adapter starts a loopback HTTP endpoint, spawns 'raft agent bridge' as a
child process, and injects content-free wake hints into the gateway session
pipeline. The agent reads/sends messages through the Raft CLI; the adapter
never touches message bodies or delivery cursors. Activity observer hooks
report tool/LLM/session lifecycle events via a bounded at-most-once queue.
Auto-enables when RAFT_PROFILE is set.

Cherry-picked from PR #47629. Authored by skyzh (@xxchan).
2026-06-19 07:52:37 -07:00
Ben 9a24e41d0f docs: add managed scope admin guide + cross-link from configuration 2026-06-19 07:46:33 -07:00
Ben Barclay 2dd285f9b3 docs(gateway): document multiplexing opt-in + contract changes
Extend the 'Running Many Gateways at Once' user-guide page with a
'one gateway for all profiles (multiplexing)' section, kept to a single page:

- How to opt in (gateway.multiplex_profiles on the default profile) and when to
  prefer it vs one-process-per-profile.
- Every contract change a user sees when the flag is on:
  1. secondary-profile 'gateway start' is a hard error (--force escape hatch),
  2. HTTP-inbound reached via /p/<profile>/ prefix; secondary profiles must NOT
     enable a port-binding platform (webhook/api_server/msgraph_webhook/feishu/
     wecom_callback/bluebubbles/sms) — config error at startup,
  3. per-credential platforms still need their own token per profile,
  4. session keys namespaced agent:<profile>: (default stays agent:main:),
  5. single PID/lock + aggregated hermes status, per-profile runtime_status.json.
- What does NOT change: per-profile .env credential isolation (stricter, incl.
  MCP/Kanban subprocess env), Kanban, profile-scoped skills/memory/SOUL, routing.

All inert when the flag is off.
2026-06-19 07:34:15 -07:00
Teknium 8b7c89bff2
feat(dashboard): session switcher panel on the Chat tab (#49077)
Add a ChatGPT-style conversation list beside the embedded TUI on the
dashboard Chat tab so users can swap sessions without leaving the page.

- New ChatSessionList component: lists recent sessions for the active
  profile (title/preview, last-active, message count, source), a New chat
  button, and a refresh control. Best-effort like ChatSidebar.
- Selecting a row drives /chat?resume=<id>, which ChatPage already treats
  as part of the PTY identity, so the terminal respawns resuming that
  conversation. Active row is highlighted; New chat clears resume.
- Wired into ChatPage as a dedicated right-side column (desktop) and into
  the existing slide-over panel above model/tools (narrow screens).
- i18n: new sessions.newChat key across all locales.
- Read-only switcher by design — delete/rename/export stay on Sessions.

Docs: web-dashboard.md Chat section documents the switcher.
2026-06-19 07:26:53 -07:00
teknium1 92451151c6 Revert "feat(skills): add html-artifact skill, fold in sketch + architecture-diagram + concept-diagrams (#48899)"
This reverts commit 9362ce2575.
2026-06-19 06:54:42 -07:00
Siddharth Balyan 9362ce2575
feat(skills): add html-artifact skill, fold in sketch + architecture-diagram + concept-diagrams (#48899)
* feat(skills): add html-artifact skill, fold in sketch + architecture-diagram + concept-diagrams

Adds a unified `html-artifact` creative skill that produces self-contained,
single-file HTML artifacts — concept explainers, implementation plans,
status/incident reports, code-review walkthroughs, technical + educational
SVG diagrams, multi-variant design comparisons, and throwaway editors that
export their state back to the clipboard. Grounded in Anthropic's
html-effectiveness gallery (MIT); the house style (token block, serif/sans/
mono split, hand-rolled diffs, inline-SVG diagrams, graceful degradation) is
distilled from reading all 20 reference files.

Supersedes and removes three overlapping skills, folding their unique value in:
- sketch              -> the fidelity dial (throwaway vs presentation) + the
                         multi-variant comparison layouts + the browser-vision
                         verify loop (references/fidelity-and-verify.md)
- architecture-diagram-> the dark "infra" token variant + double-rect masking +
                         semantic component palette (references/dark-tech.md,
                         templates/diagram.html infra mode)
- concept-diagrams    -> the 9-ramp educational color system + the concept
                         archetype library (references/concept-archetypes.md,
                         the light design system in templates/diagram.html)

Structure:
- SKILL.md (description exactly 60 chars), 6 references, 3 templates
- templates verified by headless-Chrome render + vision inspection
- editor export logic (file://-safe clipboard, Promise-normalized) verified in node

Cross-references updated in claude-design (new disambiguation table row drawing
the design-taste vs information-artifact boundary), design-md, pretext, spike,
and kanban-video-orchestrator. Website skill docs + catalogs regenerated;
stale EN/zh-Hans per-skill pages pruned and i18n cross-refs fixed.

Not folded (intentionally orthogonal): excalidraw (.excalidraw JSON), p5js
(generative canvas), claude-design / popular-web-designs / design-md (visual
design taste / brand vocab / token spec).

* feat(skills): ship html-effectiveness gallery as fetched reference examples

Add scripts/fetch-examples.sh (idempotent clone/pull of Anthropic's MIT
html-effectiveness gallery) + references/examples.md mapping each of the 20
example files to a mode so the agent reads the right worked example. The clone
lands in references/examples/ and is gitignored (it's a 384KB upstream repo,
not vendored). SKILL.md workflow + reference list now point at it; falls back to
the distilled pattern references when offline.

* feat(skills): make reading a gallery example a required authoring step

Reading the matching html-effectiveness example is now workflow step 2 (was an
optional aside in step 3): fetch the gallery, read_file the file for your mode,
mirror its structure. Models skip optional steps; the examples are the ground
truth, so consulting one is mandatory. Added an 'Example' column to the
mode->build quick-reference table and a 'don't skip the example' pitfall.

Also dogfooded the skill: read 03-code-review-pr.html and 13-flowchart-diagram.html
raw and reconciled the distilled references against source — aligned diff-row tint
opacity to the source's 0.15 (was 0.18) and added the .ctx/.hunk rows in
house-style.md + base.html so they match 03-code-review-pr.html verbatim.

* docs(skills): explain the consolidation + bundled-vs-optional rationale

The supersession note only stated *what* was folded, not *why* the prune is
sound. Expand SKILL.md's intro into a 'Why this skill exists' section: the three
former skills emitted the same artifact and overlapped, so consolidating removes
which-one-do-I-load ambiguity; and the optional->bundled promotion of
concept-diagrams is footprint-safe because this skill has zero deps (only cost is
the 60-char description; everything else is progressive-disclosure). States the
bundling dividing line explicitly: zero install cost + broadly useful gets
bundled, real install cost (hyperframes: Node+FFmpeg+Chromium) stays optional.

Regenerated website per-skill page to match.
2026-06-19 08:02:31 +00:00
Teknium c02192ff6a
feat(image-gen): add image-to-image / editing to image_generate (#48705)
* feat(image-gen): add image-to-image / editing to image_generate

Brings image generation to parity with video generation: the unified
image_generate tool now edits/transforms a source image (image-to-image)
when given image_url / reference_image_urls, routing to each backend's
edit endpoint, exactly as video_generate routes to image-to-video.

- ImageGenProvider ABC: generate() gains keyword-only image_url +
  reference_image_urls; new capabilities() declares modalities +
  max_reference_images (defaults to text-only, backward compatible).
  success_response gains a modality field; adds normalize_reference_images.
- image_generate tool: schema exposes image_url + reference_image_urls;
  dynamic schema reflects the active model's actual edit capability so the
  agent knows when image_url is honored. Handler + plugin dispatch forward
  the new inputs; legacy/text-only providers get a clear modality_unsupported
  error instead of silently dropping the source image.
- In-tree FAL: 7 models gain edit endpoints (flux-2-klein, flux-2-pro,
  nano-banana-pro, gpt-image-1.5, gpt-image-2, ideogram/v3, qwen-image)
  with per-model edit_supports whitelists + reference caps; routes to the
  /edit endpoint and skips the upscaler for edits.
- Plugins: openai (images.edit, 16 refs), xai (/v1/images/edits via
  grok-imagine-image-quality, JSON body per xAI docs), krea
  (image_style_references, 10 refs). openai-codex stays text-only and
  rejects edits with an actionable error.
- Tests: 15 new (payload, routing, dispatch forwarding, dynamic schema,
  capabilities); updated 2 change-detector/lambda tests for the new schema.
- Docs: image-generation feature page, image-gen provider plugin guide,
  tools reference.

* fix(image-gen): preserve legacy passthrough in fal/krea plugin tests

Two existing plugin tests asserted pre-image-to-image behavior:
- fal: forward image_url/reference_image_urls only when supplied, so a
  text-to-image delegation stays byte-identical (no None kwargs).
- krea: keep dict-shaped image_style_references refs verbatim (the unified
  string refs go through normalize_reference_images; legacy non-string ref
  objects pass through unchanged) — fixes KeyError when callers pass the
  richer Krea ref-object shape.

* fix(image-gen): clearer not-capable message for text-to-image-only models

When a text-to-image-only model (incl. gpt-image-2 on the Codex OAuth path,
which can't do editing through the Responses image_generation tool) gets a
source image, say 'this model is not capable of image-to-image / editing —
provide a text-only prompt' rather than sending the user shopping for other
backends. Applies to the openai-codex guard, the in-tree FAL no-edit-endpoint
error, and the dynamic tool-schema text-only line.
2026-06-18 22:13:07 -07:00
colinwren-stripe cfb55de5ea
Update Stripe Projects skill docs (#48673)
Committed-By-Agent: codex

Committed-By-Agent: codex

Committed-By-Agent: codex

Committed-By-Agent: codex

Co-authored-by: codex <noreply@openai.com>
2026-06-19 04:43:15 +00:00
flooryyyy f8d8f045fa feat(kanban): auto-subscribe calling session on kanban_create
When a worker calls kanban_create from inside a session that has a
persistent delivery channel, the originating session is now subscribed
to the new task's completion/block events automatically. The agent
that dispatched the task gets notified instead of having to poll.

- Gateway sessions (telegram/discord/slack): HERMES_SESSION_PLATFORM +
  HERMES_SESSION_CHAT_ID ContextVars, set by the messaging gateway.
- TUI / desktop sessions: HERMES_SESSION_KEY in the subprocess env.
  The TUI notification poller keys on platform='tui' + chat_id=<key>.
- CLI / cron / test: no persistent channel, no subscription.

Gated by kanban.auto_subscribe_on_create in config.yaml (default True).
Disable to mirror pre-feature behaviour — users who want explicit
kanban_notify-subscribe calls per task can set it to false. This
config gate addresses the design concern that got PR #19718 reverted
upstream (unconditional implicit auto-subscribe on tool-driven
kanban_create was too aggressive for orchestrator users).

HERMES_SESSION_ID is intentionally not a fallback channel — it is
set by ACP/agent subprocess telemetry for every invocation, not just
TUI, so treating it as a notification target would auto-subscribe
every CLI session and re-introduce the over-eager behaviour.

The kanban_create response now includes a 'subscribed' bool so
orchestrators can react if subscription failed (e.g. by falling
back to explicit kanban_notify-subscribe or to polling).

Includes 6 tests covering the gateway / TUI / CLI / partial-context /
gated / add_notify_sub-failure paths. All 90 tests in
test_kanban_tools.py pass; 509 broader kanban tests pass.
2026-06-18 14:10:51 -07:00
Teknium c37fdec2d9
feat(dashboard): surface full per-MCP catalog detail; fix pip-install doc (#48520)
The dashboard MCP catalog only showed name/description/transport and a
non-clickable source. Users couldn't see what an entry connects to or runs
before installing — the exact detail the docs trust model tells them to vet.

- /api/mcp/catalog now returns transport target (url, or command+args),
  auth_type, git install source/ref + bootstrap commands, default-enabled
  tool hint, and post-install guidance per entry.
- McpPage renders the endpoint URL (http) or command+args (stdio), the git
  install source/ref, a collapsible bootstrap-commands list, setup notes,
  and the source as a clickable link when it's a URL.
- Docs: drop the 'uv pip install -e .[mcp]' quick-start step (Hermes does
  not support pip installs; MCP ships with the standard install) and note
  the dashboard now surfaces this detail.
- Strengthen the catalog endpoint test to assert the new inspection fields.
2026-06-18 09:40:56 -07:00
qin-ctx 2a5d51c16e fix(openviking): adapt memory provider for current api
(cherry picked from commit cbb87389f33583518975fbf72671de3fd224bb28)
2026-06-18 16:58:11 +08:00
shannonsands 6092be413d
Harden hosted Docker install tree against self-modification (#47490)
* Harden hosted Docker install tree

* Document hosted Docker immutable install tree
2026-06-18 09:09:21 +10:00
Teknium 7bbffceb9c
feat(curator): make skill consolidation opt-in (prune stays default-on) (#47840)
The curator now defaults to prune-only: the deterministic inactivity pass
(mark stale / archive long-unused skills) still runs whenever the curator is
enabled, but the opinionated LLM umbrella-building consolidation fork is OFF
by default.

- agent/curator.py: add DEFAULT_CONSOLIDATE=False + get_consolidate(); gate
  the forked aux-model review in run_curator_review behind it (new consolidate
  param, None=read config). When off, the LLM pass is skipped entirely (no
  aux-model cost); the run is still recorded and reported.
- config.py: add curator.consolidate (default false); v29->v30 migration seeds
  the key for existing installs without clobbering a user-set value.
- hermes_cli/curator.py: 'hermes curator run --consolidate' override; status
  shows consolidate state; prune-only notice on run.
- docs + tests.
2026-06-17 05:20:32 -07:00
teknium 36ae958473 feat(gateway): gate message timestamps behind opt-in (default off)
Follow-up to salvaged PR #41633: the timestamp prefix injection was
unconditional. Gate the in-context render behind
gateway.message_timestamps.enabled (default false) at both the live-message
and history-replay sites; timestamp metadata is still captured + persisted
regardless so the toggle can be flipped on later. Add DEFAULT_CONFIG entry,
docs, and gate tests.
2026-06-16 15:49:59 -07:00
Jaaneek f4ef70f6fc docs(xai): update default model references to grok-build-0.1
Reflect the default-model change in the xAI Grok OAuth guide, the web
search docs (EN + zh-Hans), and the web provider docstring. grok-4.3 is
kept in the model tables as the previous default; the Nous/OpenRouter
aggregator catalog still lists grok-4.3 and is left unchanged.
2026-06-16 11:50:17 -07:00
Wolfram Ravenwolf f6a42b1acf feat(prompt): make context-file truncation limit configurable
PROBLEM: Automatic context files such as SOUL.md and AGENTS.md were capped by a hardcoded CONTEXT_FILE_MAX_CHARS value. Amy's local fork had raised that constant from 20K to 25K so a larger SOUL.md would not be silently truncated, but the hardcoded 25K value changed upstream default behavior and made the patch less generally useful.

SOLUTION: Restore the upstream-compatible 20K default, add a context_file_max_chars config setting for users who intentionally keep larger identity/project-context files, keep chat-visible truncation warnings, and document the new setting. Tests cover the default, config override, explicit max_chars precedence, and the warning text.
2026-06-16 11:28:35 -07:00
teknium1 e236bb87eb docs(skills): regenerate shop skill page after shop-app rename 2026-06-16 10:37:21 -07:00
brooklyn! c6e99ab375
Merge pull request #46959 from NousResearch/bb/composer-model-selector
feat(desktop): composer model selector, per-model presets & external-provider disconnect
2026-06-16 09:55:57 -05:00
Brooklyn Nicholson cb6b4127e7 refactor(desktop): make composer model picker sticky session state
The picker no longer touches the profile default. Model/effort/fast live as
plain UI state persisted in localStorage, so a pick follows across Cmd+N and
restarts instead of snapping back. New chats ship that state through
session.create as per-session overrides; live chats still scope switches to the
current session. Settings -> Model remains the only surface that writes the
profile default.

The gateway now accepts those session.create overrides, builds the agent with
them directly, reflects them in the immediate session.info payload, and writes
the chat's own model_config into the lazy DB row so reconnect/resume restores
that chat instead of the global default.
2026-06-16 09:50:07 -05:00
Teknium 4858942c55
fix(auxiliary): honor main fallback chain for auto tasks (#47235) 2026-06-16 06:23:24 -07:00
teknium 6373aba80f feat(gateway): rename to tool_progress_grouping, add config/docs/tests
Follow-up to salvaged PR #41620:
- Rename tool_progress_style -> tool_progress_grouping (clearer intent)
- Add display.tool_progress_grouping to DEFAULT_CONFIG (accumulate default)
- Document in messaging docs incl. 'separate is noisier, only where progress enabled'
- Add resolver tests (default/global/override/invalid/case)
2026-06-16 05:49:24 -07:00
teknium 98ae28657f feat(display): document and test memory_notifications setting
Follow-up to salvaged PR #4684:
- Add display.memory_notifications to DEFAULT_CONFIG (off|on|verbose, default on)
- Document the setting in docs/user-guide/features/memory.md
- Add resolver tests for off/on/verbose memory + skill paths
2026-06-16 05:45:40 -07:00
Teknium a6364bfa08
fix(telegram): edit streamed previews in place as rich (Bot API 10.1) (#46890)
Streamed Telegram replies that finalize through editMessageText were
converted to MarkdownV2, which has no table syntax and rewrites pipe
tables into bullet lists — users saw a table while streaming that
collapsed to a list at the last moment.

Finalize now edits the existing preview IN PLACE via Bot API 10.1's
editMessageText rich_message parameter when the content has constructs
the legacy path degrades (tables, task lists, <details>, block math).
No fresh send + delete, so no duplicate-preview flicker — the reason
#46206 reverted the fresh-final re-send path. prefers_fresh_final_streaming
stays False; the in-place edit replaces it.

- _needs_rich_rendering(): rich reserved for table/task-list/details/math
  (adapted from #45995, @YonganZhang); plain replies stay on MarkdownV2.
- _try_edit_rich(): editMessageText + rich_message via do_api_request,
  mirroring _try_send_rich's fallback/latch/transient contract.
- edit_message finalize tries rich in place before the 4,096 overflow
  pre-flight (rich cap is 32,768), falling back to legacy on rejection.
- rich_messages default flipped back to True (DEFAULT_CONFIG + adapter).
- docs (en + zh-Hans) + cli-config example updated to default-on.

Closes the root cause behind #45911 / #46009.
2026-06-16 05:26:04 -07:00
Teknium 5bfed0fe07
feat(skills): add optional payments skills (Stripe Link, MPP, Projects) (#31343)
* feat(skills): add optional payments skills (Stripe Link, MPP, Projects)

Adds four optional skills under optional-skills/payments/ wrapping the
Stripe Link CLI, the Machine Payments Protocol (MPP) clients, and the
Stripe Projects CLI plugin. Plus a router skill (payments) that picks
between them based on user intent.

All four are gated [linux, macos] — Stripe's Link CLI does not yet
support Windows. The other CLIs (mppx, stripe projects) are
cross-platform on paper but the payments cluster moves as a unit until
Link CLI gains Windows support.

Skills:
- stripe-link-cli  - one-time virtual cards + Shared Payment Tokens
- mpp-agent        - HTTP 402 payments via mppx/Tempo/Privy/AgentCash
- stripe-projects  - provision SaaS services + credential sync
- payments         - router/index skill for the cluster

Hard invariants encoded in every skill:
- Card PANs/wallet keys never enter agent transcripts, logs, or memory
- Spend approvals are not self-bypassable (Link app / wallet UI / CLI prompt)
- Final totals confirmed with user before any --request-approval call
- Credential output files cleaned up after one-time use

Zero core touches. Skills install via:
  hermes skills install official/payments/<skill>

* chore(skills/payments): drop router skill — skills shouldn't depend on other skills

Removed optional-skills/payments/payments/ — the router skill that
existed to hand off between stripe-link-cli, mpp-agent, and
stripe-projects.

Per project convention: skills should be independently loadable; a
router is a footgun because (a) it assumes the loader will follow its
recommendation rather than just loading what the user asked for, and
(b) it duplicates the trigger logic that already lives in each
sub-skill's '## When to Use' section.

The three remaining skills declare their own triggers and routing
hints. The optional-skills catalog still groups them under '## payments',
which is the appropriate place for cluster-level discoverability.

Also drops 'payments' from each remaining skill's 'related_skills' list
and removes the corresponding entries from the docs catalog + sidebars.

* feat(skills/payments): fold in danhill-stripe review feedback

- mpp-agent: add link-cli as a client option (when Link is already set
  up, or the 402 challenge advertises method="stripe")
- stripe-link-cli: reframe Link account / payment method / approval app
  as first-run setup, not hard preconditions (CLI configures them on
  first run)
- regenerate the two affected optional-skills docs pages
2026-06-15 15:28:42 -07:00
kshitij d2b34e89b0
Merge pull request #44431 from erosika/feat/honcho-identity-tree
feat(honcho): gateway-gated identity tree + canonicalize on pinUserPeer
2026-06-16 03:35:24 +05:30
Erosika 6dde7d4657 docs(memory-providers): cover gateway identity mapping for Honcho
The Honcho provider page documented the per-profile peer model (user
peer / AI peer / observation) but never the gateway axis — how platform
runtime IDs map to peers. Adds the three keys to the config table and a
short Gateway identity mapping subsection that points at the Honcho page
for the resolver ladder.

Uses the corrected pinUserPeer wording (pins non-agent users, overrides
aliases) so the provider-comparison reader gets the same accurate framing
as the dedicated page.
2026-06-15 21:50:24 +00:00
Erosika c7513df4f9 docs(honcho): clarify pinUserPeer pins only non-agent users
'everyone collapses to your peer' read as a promise about all traffic.
pinUserPeer pins the user-side peer and is checked before userPeerAliases
(session.py:335), so a pin overrides every alias — including agent peers.
For a multi-agent operator that silently pools distinct agents onto one
peer, the opposite of intent.

Scopes the wording to 'every non-agent gateway user', notes the pin
overrides aliases, and points agent-mesh operators at pinUserPeer:false +
userPeerAliases instead. Same correction in the wizard menu/echo text,
the plugin README, and the website Honcho page.
2026-06-15 21:34:09 +00:00
Austin Pickett 5f6be7f31b
fix(teams): package Microsoft Teams SDK as an installable extra (salvage #43945) (#46764)
* fix(teams): package Microsoft Teams SDK as an installable extra

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

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

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

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

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

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

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

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

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

---------

Co-authored-by: rio-jeong <rio.jeong@thebytesize.ai>
Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
2026-06-15 14:35:15 -04:00
Teknium a1f51feb72
fix(telegram): avoid rich final duplicate previews (#46206) 2026-06-14 11:13:38 -07:00
Teknium 0e22bf6439
docs(gateway): document exact silence tokens (#46105) 2026-06-14 04:37:18 -07:00
Teknium 723c2331bd fix: make profile subprocess HOME policy explicit 2026-06-14 03:20:21 -07:00
Justin Sunseri 12682d96b9 feat(telegram): restore rich messages opt-out
Salvages PR #45840's client-compatibility opt-out while keeping rich messages enabled by default via telegram.extra.rich_messages: true.
2026-06-13 21:45:49 -07:00
Teknium 9b5f7b63c6 fix(profile): make clone-from a full source selector 2026-06-13 07:33:58 -07:00
Teknium a59d5e37e8
feat(telegram): make rich messages always on (#45584)
Remove the rich_messages config toggle entirely so Telegram replies always try the Bot API 10.1 rich-message path first, with the existing MarkdownV2 fallback/latch behavior for unsupported endpoints and per-message failures.

Restore the Telegram platform hint to encourage rich Markdown tables/task lists/math now that the rich path is the default, and remove the config/docs surface for the old toggle.
2026-06-13 05:45:11 -07:00
Teknium 2681c5a12d
fix(photon): correct gateway start command (#45566) 2026-06-13 05:14:59 -07:00
xxxigm fa2aba90b4 docs(docker): explain per-profile gateway ports for multi-profile setups
The Multi-profile section never explained how to reach more than one
profile from outside the container, and distinguishes the two surfaces
that people conflate:

- Hermes Desktop's Remote Gateway connects to a `hermes dashboard`
  backend (port 9119), and a single dashboard serves every co-located
  profile via its profile switcher (the target profile is sent per
  request; the backend opens that profile's HERMES_HOME). No per-profile
  port or second connection is needed for Desktop.
- OpenAI-compatible API clients (Open WebUI, LobeChat, /v1) talk to each
  profile's API server, which binds 8642 for every profile with no
  auto-allocation. Reaching a second profile from such a client needs a
  distinct `API_SERVER_PORT` in that profile's own `.env` (and the port
  must NOT go in the container-wide `environment:` block, or every
  profile collides on it).

Adds the create -> set port -> restart flow, the bridge port-publishing
note, and clarifies the default profile's connection is untouched.
2026-06-13 05:13:25 -07:00
helix4u 1899c8f507 fix(skills): run youtube transcript helper through uv 2026-06-12 16:33:46 -07:00
Teknium 7a318aae22
fix(profiles): exclude session history, backups, and snapshots from --clone-all (#45246)
--clone-all copied the source profile's state.db, sessions/, backups/,
state-snapshots/, and checkpoints/ into the new profile. These are
per-profile history: a 49GB copy in practice (15GB snapshots + 11GB
backup archives + 16GB state.db + 6.4GB sessions), and restoring a
copied backup inside the clone would resurrect the SOURCE profile's
state. A clone is a fresh workspace; history stays with the source.

New _CLONE_ALL_HISTORY_EXCLUDE_ROOT set, applied at root level for ANY
source profile (named profiles accumulate the same artifacts), unlike
the default-gated infrastructure excludes. Nested same-name dirs still
copy. Docs and the post-create CLI message updated to match; profile
export / hermes backup remain the full-history paths.
2026-06-12 15:41:50 -07:00
Teknium bba9b519aa
fix(delegation): remove the default subagent wall-clock timeout (#45149)
Subagents doing legitimate heavy work (deep code reviews, research
fan-outs, slow reasoning models) were routinely killed at the blanket
600s child_timeout_seconds cap while making steady progress (e.g. 36
API calls completed when the axe fell). Failures should come from what
the child is actually doing — API errors, tool errors, iteration
budget — not a delegation-level stopwatch.

- DEFAULT_CHILD_TIMEOUT: 600 -> None; Future.result(timeout=None)
  blocks until the child finishes
- config default delegation.child_timeout_seconds: 600 -> 0
  (0/negative = disabled; positive opts back in, floor 30s unchanged)
- stuck-child protection unchanged: the heartbeat staleness monitor
  still stops refreshing parent activity so the gateway inactivity
  timeout fires on a truly wedged worker; the 0-API-call diagnostic
  dump still works when a cap is configured
- docs updated (EN + zh-Hans)
2026-06-12 12:58:25 -07:00
Teknium 9b01c4d193
fix(update): never spawn an interactive polkit prompt when restarting a system-scope gateway (#45145)
When hermes update restarts a hermes-gateway system service as a
non-root user, the systemctl reset-failed/start/restart calls trigger
polkit's org.freedesktop.systemd1.manage-units TTY authentication
agent. That prompt runs inside a captured subprocess with a 10-15s
timeout, so it flashes and dies before the user can answer, and the
resulting TimeoutExpired was swallowed silently by the loop's blanket
except — the restart phase just vanished with no output.

- Resolve a manage-units command prefix up front: plain systemctl as
  root, sudo -n systemctl as non-root (with a targeted reset-failed
  probe so least-privilege sudoers entries scoped to hermes-gateway*
  qualify), or None when no non-interactive privilege path exists.
- Add --no-ask-password to every manage-units call in the update
  restart path so polkit can never prompt inside a captured subprocess.
- When unprivileged: after a graceful drain, rely on systemd's own
  RestartSec auto-restart (needs no privileges) with a message about
  the wait; skip the force-restart fallback with clear manual
  instructions instead of racing a doomed polkit prompt.
- Surface TimeoutExpired in the restart loop instead of passing
  silently, and add sudo to the system-scope recovery hints.
- Docs: headless-VM note recommending user service + enable-linger,
  or sudo updates / a scoped NOPASSWD sudoers entry for system
  services.
2026-06-12 12:38:15 -07:00
Teknium 652dd9c9f2 fix: rich messages follow-ups — reply_parameters, send latch, opt-in default
- Use reply_parameters per the sendRichMessage spec instead of the
  undocumented reply_to_message_id scalar (silently ignored -> reply
  anchor quietly dropped).
- Latch rich sends off after an endpoint-capability failure (old PTB /
  server without sendRichMessage) so every later reply doesn't pay a
  doomed extra roundtrip; per-message BadRequests do NOT latch.
- Default rich_messages to OFF (opt-in) while the day-old Bot API 10.1
  endpoint is validated live; revert the prompt-hint table guidance
  until the default flips on.
- Tests: reply_parameters shape, send-latch behavior, BadRequest
  non-latch; rich tests opt in explicitly via extra.
2026-06-12 11:47:54 -07:00
ITheEqualizer 05b9c84ca4 Add Telegram Bot API 10.1 rich message support
Introduce opportunistic support for Telegram Bot API 10.1 rich messages by sending raw agent Markdown via sendRichMessage and streaming previews via sendRichMessageDraft. Implements a rich-path fast‑path in gateway/platforms/telegram.py (RICH_MESSAGE_MAX_BYTES=32768, feature gate platforms.telegram.extra.rich_messages, bot capability checks, routing/thread handling, and conservative fallback rules: permanent/capability errors fall back to the legacy MarkdownV2 path, transient/network errors are surfaced without legacy-resend). Also add a latch for draft capability failures (_rich_draft_disabled) and preserve legacy chunking and draft behavior when needed. Update agent prompt hints (telegram encourages rich Markdown/tables), add CLI config example option, update English and Chinese docs to describe rich messages and fallbacks, and add/adjust tests for rich send and draft behavior.
2026-06-12 11:47:54 -07:00
Teknium c196269d8d
fix(credits): suppress usage gauge when top-up funds exist + add display.credits_notices toggle (#44716)
The subscription-cap usage gauge (50/75/90% bands) ignored purchased
(top-up) credits: a sub user with top-up funds got a sticky warn banner
at 90% of their cap — permanently at >=100%, alongside grant_spent —
despite being fully able to keep inferencing. The cap is the wrong
denominator for an account that can keep spending.

- evaluate_credits_notices: purchased_micros > 0 suppresses the usage
  band (grant_spent already covers the cap-reached + top-up case with
  the remaining balance). A top-up landing mid-session clears any
  showing band; spending top-up down to 0 resumes the gauge.
- New display.credits_notices config (default true): false silences all
  credits notices. State capture and /usage are unaffected. Read once
  per agent (cached) in _emit_credits_notices, fail-open true.
- Docs: configuration.md display block.
2026-06-12 01:06:46 -07:00
Erosika 1544813bfe chore(honcho): replace example Telegram UID with placeholder 2026-06-11 15:06:07 -04:00
Erosika 2708c33c75 docs(honcho): anonymize example peer name to alice 2026-06-11 15:04:01 -04:00
Erosika 23a7458acf docs(website): cover gateway identity mapping in Honcho feature page
The identity-mapping keys never made it to the site docs. Add the three keys
to the config reference and a Gateway Identity Mapping section: when it
applies (gateway only, setup-gated), the intent tree, resolver order, the
un-pin orphan warning, and the deprecated pinPeerName alias.
2026-06-11 14:58:19 -04:00
Teknium 2ecb4e62bb
Merge remote-tracking branch 'origin/main' into hermes/hermes-6b48295e 2026-06-11 07:38:25 -07:00
Chris 4717989c10
fix(matrix): isolate room context and restore reliable inbound dispatch (#18505)
* fix(matrix): isolate room context and inbound dispatch

* test(matrix): cover room isolation and dispatch regressions

* docs(matrix): document room isolation and session scope

* fix(matrix): stabilize CI requirement checks

* test(matrix): isolate mautrix stubs in requirements tests

* fix(matrix): port room-scoped status and resume to slash commands mixin

Move Matrix /status scope output and /resume same-room guards from the
pre-refactor gateway/run.py into gateway/slash_commands.py so PR #18505
foundation behavior survives the upstream god-file decomposition.

Uses i18n keys for Matrix resume/status messages. Preserves upstream
session.py fixes (role_authorized, DM user_id isolation).

* docs(matrix): explain inbound dispatch via handle_sync loop

Document why Hermes uses an explicit sync loop with handle_sync() rather than
client.start(), aligning with upstream #7914 diagnostics while preserving
Hermes background maintenance tasks.

* fix(i18n): add Matrix resume/status keys to all locale catalogs

The Matrix /resume and /status slash-command keys added in the foundation
PR must exist in every supported locale file. tests/agent/test_i18n.py
asserts key and placeholder parity across catalogs.

Non-English locales use English strings as interim placeholders until
community translators can localize them.

* fix(matrix): restore gateway authz for allowed_users; honor config require_mention

Revert the early MATRIX_ALLOWED_USERS gate in _on_room_message so inbound
sender authorization stays in gateway authz like main. Parse require_mention
from config.extra (platforms.matrix / top-level matrix yaml) with env fallback,
matching thread_require_mention and fixing Forge when require_mention is set
only in profile config.yaml.

* fix(matrix): harden status scope and allowlisted DMs

* fix(matrix): use session store lookup for resume scope
2026-06-11 07:41:43 -04:00
Teknium 875aa8f162
feat(dashboard): unify multi-profile management — one machine dashboard, global profile switcher (#44007)
* feat(dashboard): unify multi-profile management — one machine dashboard, global profile switcher

The dashboard becomes a machine-level management surface with one
write-target selector, replacing per-profile dashboard fragmentation.

Backend:
- profile param (query or body) on /api/config (get/put/raw), /api/env
  (get/put/delete/reveal), /api/mcp/servers (list/add/remove/test/enabled),
  /api/mcp/catalog (list/install), /api/model/info, /api/model/set —
  all scoped through the existing _profile_scope() context manager
- model/set restructured: expensive-model warning (await) runs before the
  scope; the config write runs sync inside the scope in a worker thread
- MCP catalog installs + git-bootstrap entries spawn 'hermes -p <profile>'
- chat PTY: ?profile= on /api/pty points the child's HERMES_HOME at the
  profile dir (its own gateway subprocess, config/skills/memory/state.db
  all profile-bound); in-process gateway attach skipped when scoped

CLI launch unification:
- '<profile> dashboard' routes to the machine dashboard: attach (open
  browser at ?profile=) when one is listening, else re-exec pinned to the
  default profile with --open-profile preselecting the launcher
- --isolated preserves the old dedicated per-profile server behavior
- start_server(initial_profile=...) appends ?profile= to the auto-open URL

Frontend:
- ProfileProvider + sidebar ProfileSwitcher: ONE global selector, URL-
  persisted (?profile=), mirrored into fetchJSON which auto-appends the
  param to the scoped endpoint families (explicit params win)
- app-wide amber banner names the managed profile
- SkillsPage's page-local selector (from the skills-scoping PR) folded
  into the global context — single source of truth
- ChatPage threads the scope into the PTY WS URL; switching profiles
  remounts the terminal into a fresh scoped session

Omitted profile keeps legacy behavior everywhere.

* docs(dashboard): document machine-level multi-profile management

- web-dashboard.md: 'Managing multiple profiles' section (switcher, URL
  deep-links, unified launch, --isolated, scoped Chat, what stays
  per-profile) + --isolated in the options table
- profiles.md: 'From the dashboard' subsection + set-as-active vs
  switcher clarification
- cli-commands.md: --isolated flag + profile-alias launch example

* fix(dashboard): address profile-unification review findings

Review findings (dev review on PR #44007):

1. HIGH — stale page state on profile switch: pages load data on mount
   and didn't consume the profile scope, so a page opened under profile A
   kept showing A's state while writes silently targeted the newly
   selected B. Fixed structurally: ProfileKeyedRoutes wraps the routed
   page tree and keys it by the selected profile, remounting every page
   (fresh state + refetch) on switch. ChatPage keeps its own remount
   (channel keyed on scopedProfile).

2. HIGH — /api/model/auxiliary read was unscoped while /api/model/set
   wrote scoped (Models page could show default's aux pins while editing
   worker's). Endpoint now takes profile + _profile_scope, added to
   PROFILE_SCOPED_PREFIXES, HTTPException re-raise so ghost profiles 404
   instead of 500. Regression test asserts read/write symmetry with
   differing worker/default aux config.

3. MEDIUM — tools post-setup spawned unscoped from the profile-aware
   drawer. Now spawns 'hermes -p <profile> tools post-setup <key>'
   (same mechanism as hub installs); drawer threads its profile prop.
   Most hooks install machine-level artifacts where the scope is inert,
   but hooks reading config/env now see the drawer's HERMES_HOME.

4. LOW — ty warnings: env Optional asserts before subscript/membership,
   fastapi import replaced with web_server.HTTPException re-use.

298 tests green across the four affected suites; tsc -b + vite build
green; aux scoping E2E-verified with real imports.

* fix(dashboard): address second profile-unification review (gille)

1. BLOCKER — profile scope dropped on sidebar navigation: ProfileProvider
   derived the selection from the current URL, and nav links are bare
   paths, so clicking Config from /skills?profile=worker silently reset
   the write target. State is now the source of truth; an effect
   re-asserts ?profile= onto the new location after every navigation
   (URL stays a synchronized projection for deep links/refresh), and an
   incoming URL param (e.g. 'Manage skills & tools' links) still wins.

2. BLOCKER — /api/model/options unscoped while model/set wrote scoped:
   the picker context (current model/provider, custom providers,
   per-profile .env auth state) now loads inside _profile_scope; added
   to PROFILE_SCOPED_PREFIXES. Test: a worker-only current-model pin
   appears in the scoped payload and not the unscoped one.

3. BLOCKER — MCP test-server probe escaped the scope after the config
   read: the probe now re-enters _profile_scope inside the worker thread
   so env-placeholder expansion resolves against the selected profile's
   .env. Known limit (documented): the probe's dedicated MCP event-loop
   thread doesn't inherit the contextvar (OAuth token paths). Test
   asserts get_hermes_home() inside the probe == the worker profile dir.

4. BLOCKER — broad excepts swallowed unknown-profile 404s: /api/model/info
   degraded to 200-with-empty-model-info and /api/mcp/catalog to a
   silently-empty catalog. Both re-raise HTTPException; 404 regression
   tests added for info/options/catalog.

Polish: scope banner clears the fixed mobile header (mt-14 lg:mt-0);
--open-profile hidden via argparse.SUPPRESS (internal re-exec flag);
attach-path test now asserts the opened ?profile= URL.

(Stale-page-state + /api/model/auxiliary findings from this review were
already fixed in 92bcd1568 — the review ran against e600f6951.)

35 tests in the two new suites + 274 in the adjacent ones, all green;
tsc -b + vite build green; scoping E2E-verified with real imports.

* docs(dashboard)+fix: self-review pass — Profiles page section, REST profile-param tip, body-beats-query precedence

Docs:
- web-dashboard.md: add the missing 'Profiles' subsection to Pages
  (cards, create/builder, manage-skills jump, set-as-active vs switcher
  distinction, editors); REST API section gets a profile-scoped-endpoints
  tip documenting ?profile= / body profile / 404 semantics / /api/pty
- (profiles.md + cli-commands.md were already updated in e600f6951)

Precedence fix: scoped endpoints taking BOTH a query param and a body
field now resolve body.profile first. The SPA's fetchJSON injects the
query param from the GLOBAL switcher; an explicit body.profile (e.g.
Profile Builder flows writing into a specific new profile) is the more
specific intent and must not be overridden by whatever the sidebar
happens to be set to. Matches the documented 'explicit beats global'
contract in api.ts.

Verified: 304 tests green across the four suites; tsc -b + vite build
green; docusaurus build green (only pre-existing broken-link warnings,
none from this PR's pages).
2026-06-11 03:29:33 -07:00
Teknium 7d8d000b19
revert(cron): remove per-job profile support (PR #28124) (#43956)
Fully removes the cron per-job 'profile' arg added in #28124: the
cronjob tool schema field, CLI --profile flags on cron create/edit,
job-record storage/validation, the scheduler's _job_profile_context
wrapper, and the script-runner env override. Sequential-partition
logic reverts to workdir-only.

The context-local HERMES_HOME override in hermes_constants and the
subprocess bridging in tools/environments/local.py are kept — they
now have other consumers (dashboard multi-profile, TUI gateway).
2026-06-10 20:46:17 -07:00
Teknium acd7932c0f
docs: cross-link write-approval gate from skills, configuration, and slash-command docs (#43801)
The memory/skill write-approval gate (#38199, #43354, #43452) was only
documented inside features/memory.md. Surface it everywhere users will
actually look:

- features/skills.md: new 'Gating agent skill writes' section under
  skill_manage, with the staging semantics, review commands, and the
  distinction from skills.guard_agent_created
- configuration.md: memory.write_approval added to the Memory
  Configuration block; new 'Write approval for skill writes' subsection
  next to the guard_agent_created scanner
- reference/slash-commands.md: /memory and /skills review subcommands in
  both the CLI and messaging tables; Notes updated since /skills
  pending/approve/reject/diff/approval now works on the gateway
- features/memory.md: cross-link to the new skills section
2026-06-10 19:54:44 -07:00
emozilla bfcc9f92b4 Merge commit '6110aed9b' into feat/whatsapp-cloud-api 2026-06-10 21:39:22 -04:00
xxxigm 88fcf0c8c0 docs(memory): clarify that memory does not auto-compact when full
The "Persistent Memory" callout said "when memory is full, the agent
consolidates or replaces entries to make room," which reads as if the
store self-compacts automatically. It does not: the `memory` tool
returns an overflow error and the agent does the consolidation in-turn
(the design from #41755). Also note that `replace` is bound by the same
limit — swapping in a longer entry can still overflow — which is the
exact case that confused a user (replace rejected near the cap even
though the math was correct).
2026-06-10 14:39:50 -07:00
Barron Roth 2c19208224 feat(tts): add Gemini audio tag rewrite 2026-06-10 02:57:39 -07:00
Barron Roth 5718811de0 feat(tts): add Gemini persona prompt file 2026-06-10 02:57:39 -07:00
Teknium 70d5d7e39b
fix(memory,skills): repair write-approval inline prompt, gateway staging, and gateway /skills review (#43452)
Follow-ups to #38199/#43354 found in post-merge review:

- Inline CLI memory approval never worked: the per-thread approval callback
  was not passed to prompt_dangerous_approval, so the prompt_toolkit
  fail-closed guard (#15216) denied every gated foreground write without
  showing a prompt. Now invokes the registered callback directly; a crashed
  prompt falls back to staging instead of a silent deny.
- Gateway sessions claimed inline support but prompt_dangerous_approval has
  no gateway round-trip (that lives in the pending-approval queue), so gated
  gateway memory writes hit the input() fallback and denied. Gateway
  contexts now stage for /memory pending review.
- /skills pending|approve|reject|diff|approval now works on the gateway
  (gateway_config_gate on skills.write_approval), so skills staged from a
  messaging session can be reviewed there. Diff output truncated for chat.
- memory_tool validates required params before the gate so invalid writes
  are rejected immediately instead of staged and failing at approve time.
- Stale tri-state write_mode docstrings updated to the boolean gate; docs
  table corrected (inline prompt is interactive-CLI-only).
- 6 new tests covering the interactive approve/deny/error paths, gateway
  staging, skills never-prompt invariant, and pre-gate validation.
2026-06-10 02:57:15 -07:00
Siddharth Balyan 183d86b3e0
fix(openrouter): route reasoning_effort to verbosity for adaptive Anthropic models (#43436)
* fix(openrouter): route reasoning_effort to verbosity for adaptive Anthropic models

Reasoning-mandatory Anthropic models (Claude 4.6+/fable/mythos-class) over
OpenRouter ignore reasoning.effort and use adaptive thinking. #42991 correctly
stopped Hermes from sending a reasoning field to them (it 400s), but put nothing
in its place — leaving agent.reasoning_effort a silent no-op on the OpenRouter
path: the model always ran at its adaptive default (high) regardless of config.

OpenRouter honors the requested effort on the top-level verbosity field instead
(maps to Anthropic output_config.effort). Route the existing
reasoning_config[effort] there for these models while still never emitting a
reasoning field, preserving the #42991 fix. No new config arg — the value the
user already sets via agent.reasoning_effort now flows to verbosity.

- low/medium/high/xhigh/max pass through verbatim (OpenRouter accepts the
  extended scale for Claude; verified live HTTP 200 + monotonic token spend).
- effort unset/none/disabled omits verbosity so the model keeps its default.
- native Anthropic transport already correct; unchanged.

Fixes #43432

* test(openrouter): cover real effort range (add minimal, frame max as passthrough)

Adversarial review noted the verbosity tests looped over 'max' — a value
parse_reasoning_effort can never produce — while omitting 'minimal', which it
can. Align the routing test with the real config range
(VALID_REASONING_EFFORTS = minimal/low/medium/high/xhigh) and keep a separate
value-agnostic passthrough test that documents why xhigh/max must survive
verbatim (TypedDict, no runtime literal validation; OpenRouter accepts the
extended scale for Claude).

* docs: explain reasoning_effort -> verbosity routing for adaptive Anthropic models

Document that reasoning_effort transparently maps to OpenRouter's verbosity
field for adaptive-thinking Anthropic models (Claude 4.6+/Fable/Mythos), where
reasoning.effort is ignored. Note xhigh is the configurable ceiling (max is wire-
only). Add verbosity as a top-level-kwarg example in the provider-plugin guide.
2026-06-10 15:03:01 +05:30
Teknium cd9a9cd8e5
fix(gateway): Slack approval UX in threads — block-size overflow + typed-prefix instruction text (#43444)
Two fixes for the reported Slack thread approval UX:

1. Slack Block Kit approval/confirm sends silently overflowed the
   3000-char section-block cap (flat 2900-char truncation + header +
   reason), so long execute_code approvals failed with invalid_blocks
   and fell back to the plain-text prompt with no buttons. Budget the
   command preview against the rendered fixed parts so blocks never
   exceed the cap (send_exec_approval + send_slash_confirm).

2. The text fallbacks told users to reply /approve — which Slack blocks
   inside threads and Matrix clients reserve client-side. Add a
   typed_command_prefix capability flag on BasePlatformAdapter
   (default "/"; Slack and Matrix set "!" to match their existing
   bang-prefix rewrite) and use it in the shared fallback prompt
   builders (exec approval, update prompt, destructive slash confirm,
   expensive-model confirm) plus Matrix's reaction-prompt text.
   The slash-confirm text-intercept now also accepts bang-prefixed
   replies (!always, !cancel) since those keywords aren't registered
   commands and the adapters' rewrite doesn't touch them.
2026-06-10 02:30:01 -07:00
Teknium 095f526b11
refactor(memory,skills): replace tri-state write_mode with boolean write_approval (default off) (#43354)
The shipped tri-state write_mode (on|off|approve) conflated two concepts —
whether writes are enabled and whether they're gated — so 'on' (writes flow
freely, gate inactive) read like 'gating is on'. Replace it with a single
clear boolean gate that defaults off.

  memory.write_approval / skills.write_approval:
    false (default) — write freely; the approval gate is off (pre-gate behaviour)
    true            — require approval: memory foreground prompts inline, memory
                      background-review + all skill writes stage for review

The old 'off = block all writes' mode is dropped; memory_enabled: false already
disables memory entirely, so a third 'block' state was redundant.

- tools/write_approval.py: get_write_mode/MODE_* → write_approval_enabled() bool;
  evaluate_gate() loses the config-driven 'blocked' path (blocked now only comes
  from an interactive user denial).
- tools/memory_tool.py, tools/skill_manager_tool.py: comment + behaviour follow.
- hermes_cli/config.py: memory/skills write_mode → write_approval (False);
  _config_version 28→29 with a 28→29 migration that renames any persisted
  write_mode (approve→true, on/off/unset→false) and drops the old key.
- slash commands: '/memory|/skills mode <on|off|approve>' → 'approval <on|off>'
  ('mode' kept as a back-compat alias); set_mode_fn callback now takes a bool.
- write_approval_commands.py, cli_commands_mixin.py, gateway/slash_commands.py,
  commands.py: handlers + registry args/subcommands updated.
- docs + tests rewritten for the boolean model; added migration tests.
2026-06-09 23:21:14 -07:00
Teknium 96af61b6ef
feat(memory,skills): approve/deny gate for memory + skill writes (#38199)
Adds memory.write_mode and skills.write_mode (on|off|approve), applied to
both foreground turns and the background self-improvement review fork — the
source of the unprompted 'wrong assumption' saves users reported.

- on (default): write freely, unchanged behaviour
- off: never write; the tool returns a clean disabled result
- approve: don't commit. Memory foreground writes prompt inline (small,
  reviewable in a chat bubble); background memory writes and ALL skill writes
  stage to a pending store instead (a SKILL.md is too large to review inline,
  and a daemon thread can't block on a prompt)

Review staged writes from CLI or any messaging platform:
  /memory pending|approve|reject|mode
  /skills pending|approve|reject|diff|mode

Skill review respects the size asymmetry: inline you see a one-line gist;
the full unified diff stays out-of-band (/skills diff, dashboard, or the
staged JSON file).

New: tools/write_approval.py (gate + pending store), hermes_cli/
write_approval_commands.py (shared CLI+gateway handlers). Gates wired at the
single entry points memory_tool() and skill_manage(), using the existing
write-origin ContextVar to distinguish foreground from background_review.
2026-06-09 21:51:43 -07:00
Teknium fdc90346ea
chore(skills): move red-team skills (godmode, obliteratus) to optional-skills — Anthropic classifier (#43221)
* chore(skills): remove red-team skills (godmode, obliteratus) from bundled catalog

Anthropic's output classifier on claude-fable-5 (and likely other Claude
models served through it) intermittently returns empty content for sessions
whose system prompt advertises these skills. The bundled skills-catalog block
is injected into every session's system prompt, so the descriptions

  - red-teaming/godmode      'Jailbreak LLMs: Parseltongue, GODMODE, ULTRAPLINIAN'
  - mlops/inference/obliteratus 'OBLITERATUS: abliterate LLM refusals (diff-in-means)'

trip the classifier on EVERY session regardless of which skill is actually
loaded, killing unrelated legitimate work (PR review, codebase audits, etc.).

Measured impact (controlled, interleaved A/B, claude-fable-5 via OpenRouter,
prompts differing only by the ~204 chars of these catalog lines, N=20 each):
  catalog lines present -> 19/20 (95%) blocked
  catalog lines absent  -> 5/20  (25%) blocked

Removing them ~quartered the block rate. Rewording the descriptions was not
enough; the skills must leave the bundled catalog.

- Delete skills/red-teaming/godmode and skills/mlops/inference/obliteratus
- Drop their generated doc pages + catalog/sidebar entries (EN + zh-Hans)
- Drop the godmode hand-written-page exception in generate-skill-docs.py

* chore(skills): relocate godmode + obliteratus to optional-skills

Rather than deleting outright, move both into optional-skills/ so they remain
installable via `hermes skills install` while leaving the always-injected
bundled catalog (which is what tripped Anthropic's classifier).

- optional-skills/security/godmode  (was skills/red-teaming/godmode)
- optional-skills/mlops/obliteratus  (was skills/mlops/inference/obliteratus)
- regenerate optional-skills catalog + sidebar entries
2026-06-09 21:41:00 -07:00
Ben Barclay 5cf6e28a2f
fix(gateway): auto-start after container restart via planned-stop marker (#42675) (#43236)
* fix(gateway): auto-start after container restart via planned-stop marker

On Docker (s6-overlay), the gateway runs as a dynamically-registered s6
service. When the container stops/restarts/upgrades, s6 sends the gateway
a plain SIGTERM. The shutdown path (_stop_impl) ended with an
unconditional _update_runtime_status("stopped"), persisting
gateway_state=stopped to the volume. container_boot.py reads that on the
next boot and only auto-starts gateways whose last state was "running"
(_AUTOSTART_STATES) — so after a routine `docker compose up
--force-recreate` the gateway stays down and messaging channels silently
go dark, with no error surfaced (issue #42675).

The codebase already distinguishes intentional stops from unexpected
signals via the planned-stop marker (write_planned_stop_marker /
consume_planned_stop_marker_for_self): `hermes gateway stop`,
systemd/launchd ExecStop, and Ctrl+C write a marker before signalling,
so the handler classifies them as planned. An unmarked SIGTERM
(container/s6 restart, OOM, bare kill) is signal-initiated.

This wires that existing classification through to the state persist,
rather than adding unreliable signal-source inference:

- run.py: GatewayRunner._signal_initiated_shutdown, set in
  shutdown_signal_handler's unmarked-signal branch. In _stop_impl, a
  signal-initiated (non-restart) teardown now persists "running" instead
  of "stopped" — preserving the operator's run-intent and overwriting the
  mid-shutdown "draining" marker so _AUTOSTART_STATES matches on reboot.
  Operator stops and restarts persist "stopped" as before.

- service_manager.py: S6ServiceManager.stop() now writes the planned-stop
  marker for the supervised PID (read from s6-svstat) before `s6-svc -d`,
  so an in-container `hermes gateway stop` is correctly classified as
  intentional (parity with the systemd/launchd/host stop paths, which
  already mark). Best-effort: a marker-write failure falls back to the
  safe signal-initiated path.

Tests: shutdown persist-decision table (signal→running, operator→stopped,
restart→stopped), s6 stop marker write + svstat PID parse + failure
tolerance. The signal→running and s6-marker tests fail without the
respective source change. Verified end-to-end against a container built
from this branch: an unmarked SIGTERM to the live gateway leaves
gateway_state=running (shutdown-context log confirms signal path);
existing real container-restart suite still green.

* docs(docker): clarify gateway autostart distinguishes operator-stop from container-kill

The per-profile-supervision section described the autostart-across-restart
contract as "running gateways come back, stopped stay stopped" without
spelling out what records 'stopped'. That contract was the source of
#42675 confusion: users expected a restart to bring the gateway back and
it didn't. With the write-side fix, only an explicit `hermes gateway stop`
records 'stopped'; container/s6 restart SIGTERMs (incl. image upgrades and
unexpected exits) leave the state 'running' so the gateway auto-starts.
Make that distinction explicit in both the multi-profile and
per-profile-supervision sections.

* test(docker): real-restart autostart E2E for #42675

Adds test_live_gateway_autostarts_after_real_restart_without_manual_state_stamp:
a live s6-supervised gateway is killed by an actual `docker restart`
SIGTERM (no manual gateway_state stamp, no planned-stop marker) and must
auto-start on the next boot. Exercises the WRITE side of the fix that the
existing stamp-based tests bypass.

Verified to FAIL against an origin/main image (reconciler logs
prior_state=stopped action=registered — the #42675 bug) and PASS against
the fixed image (prior_state=running action=started).
2026-06-10 14:01:34 +10:00
xxxigm 02f878ec5a
docs(windows): correct native data dir to %LOCALAPPDATA%\hermes (#42856)
* docs(windows): correct native data dir to %LOCALAPPDATA%\hermes

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

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

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

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

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

The native Windows installer wrote config/env/skills under $HermesHome
(%LOCALAPPDATA%\hermes) but its success messages claimed ~/.hermes,
which doesn't exist on native Windows. Print the actual paths so a new
user can find their config, .env, and skills.
2026-06-09 14:11:20 -05:00
brooklyn! ba44de06da
fix(install): self-heal a stuck Electron download (salvage of #42894) (#42998)
* fix(install): self-heal a stuck Electron download on the desktop build

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

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

* test(install): cover the Electron mirror fallback

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

* docs(desktop): troubleshoot a stuck Electron download

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

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

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

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

---------

Co-authored-by: xxxigm <tuancanhnguyen706@gmail.com>
2026-06-09 18:19:14 +00:00
underthestars-zhy 0337658904 fix(photon): migrate user API calls to Spectrum backend
Switch `list_users`, `find_user_by_phone`, `create_user`,
`register_user_if_absent`, and `refresh_user_numbers` from the
Dashboard API (Bearer token) to the Spectrum API (Basic auth with
project credentials). Update response unwrapping to handle the nested
`data.users` envelope returned by Spectrum, add `_spectrum_host()`
resolver, `_basic()` header helper, and structured error helpers.
Update tests, docs, and plugin.yaml accordingly.
2026-06-08 22:53:01 -07:00
underthestars-zhy b58ff93459 feat(photon): persist and display user phone numbers in status
Store operator and assigned iMessage numbers in `auth.json` after
setup, and surface them in `hermes photon status`. When numbers are
missing, status auto-refreshes from the dashboard without provisioning
new lines.
2026-06-08 22:53:01 -07:00
underthestars-zhy 4e4d27875f feat(photon): gRPC-native iMessage channel (no webhook)
Make Photon iMessage a first-class persistent-connection channel like
Discord/Slack, using the spectrum-ts gRPC stream for both directions.

- Inbound: the sidecar forwards the SDK's app.messages gRPC stream to the
  adapter over a loopback GET /inbound (NDJSON) instead of webhooks. Drops
  the aiohttp webhook server, HMAC signature verification, public URL, and
  PHOTON_WEBHOOK_* config; adapter reconnects with backoff.
- Management plane: device login uses client_id=photon-cli against the
  single dashboard host (Bearer), matching the official photon-hq/cli;
  find-or-create "Hermes Agent" project, enable Spectrum, rotate secret,
  register user (with phone dedup), surface the assigned iMessage line.
- SDK projectId is the project's spectrumProjectId, not the dashboard id;
  runtime creds persist to ~/.hermes/.env like every other channel.
- CLI: 6-step setup, webhook subcommands removed.
- Tests/docs updated for the gRPC flow; sidecar pins spectrum-ts ^1.17.1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 21:03:58 -07:00
Juraj Bednar 0c2e81df00 feat(simplex): groups, native attachments, text batching, auto-accept
Salvage of PR #27978 cherry-picked onto current main, resolving conflicts
with main's intervening SimpleX plugin fixes (resp-envelope normalization,
health-monitor reconnect-churn fix, bare-form DM addressing).

What's new:
- Group support via SIMPLEX_GROUP_ALLOWED (comma-separated IDs or '*');
  inbound items surface chat_id=group:<id> + chat_type=group. Disabled by
  default so a bot in a group doesn't process every member's traffic.
- Inbound files/voice via rcvFileDescrReady (immediate /freceive) deferred
  through _pending_file_transfers, replayed on rcvFileComplete. Voice notes
  -> MessageType.VOICE.
- Native outbound media: send_image (PNG/JPEG + inline thumbnail), send_voice
  (msgContent.type=voice), send_video, send_document. All addressed by numeric
  ID via /_send ... json [...].
- MEDIA:<path> tags in agent replies stripped and dispatched as voice/document.
- Text-burst batching (HERMES_SIMPLEX_TEXT_BATCH_DELAY, default 0.8s).
- Auto-accept contact requests (SIMPLEX_AUTO_ACCEPT, default true).
- Group send path uses structured /_send #<id> json form (the bracket
  #[<id>] form is parsed as display-name lookup and silently drops).

plugin.yaml bumped to 1.1.0; docs updated. All inside plugins/platforms/simplex/
- no core edits.

Co-authored-by: Juraj Bednar <juraj@bednar.io>
2026-06-08 21:03:45 -07:00
Teknium 4615e08d3d
feat(photon): wire outbound media via spectrum-ts attachment() (#42397)
Photon now exposes attachment send (Ray Sun, photon-nousresearch), so
the Photon plugin gains outbound media to match the BlueBubbles iMessage
channel.

- sidecar: new /send-attachment endpoint wrapping space.send(attachment())
  / space.send(voice()); caption sent as a trailing text bubble.
- adapter: override send_image/send_image_file/send_voice/send_video/
  send_document/send_animation. URL helpers cache to a local path first
  (cache_image_from_url), file helpers pass through. Defense-in-depth
  path re-validation before the path reaches the Node sidecar.
- _standalone_send (cron): send text first, then each media_file as a
  /send-attachment call (is_voice -> voice builder).
- docs/README: flip the 'outbound attachments not wired' note.
2026-06-08 15:29:16 -07:00
Robin Fernandes 639c1e3636 feat(sessions): add optional max session cap 2026-06-08 15:12:12 -07:00
teknium1 1866518574 feat(photon): group-chat mention gating for full channel parity
Adds the last missing parity piece vs the established channels: group
chats can be made opt-in via a mention wake word, exactly like the
BlueBubbles iMessage channel.

- require_mention + mention_patterns, read from config.extra (config.yaml
  via the generic gateway bridge) or PHOTON_REQUIRE_MENTION /
  PHOTON_MENTION_PATTERNS env vars. Same shapes BlueBubbles accepts
  (list / JSON / comma / newline), same default Hermes wake words.
- _dispatch_inbound drops unmatched group messages and strips the leading
  wake word from matched ones; DMs are never gated.
- plugin.yaml + docs document both knobs and the config.yaml form.
- New test_mention_gating.py (8 tests): default-off, group drop/pass,
  wake-word strip, DM bypass, custom patterns, env comma-list, invalid
  regex skip.

The config.yaml -> extra bridge needed no core change — the generic
shared-key loop in gateway/config.py already iterates plugin platforms
(_shared_loop_targets += plugin_entries()), so require_mention /
mention_patterns flow through automatically.

Note: outbound media is the one capability Photon still can't reach —
Photon exposes no HTTP send-attachment endpoint yet (documented API
limitation), so the sidecar can't send files. Not faked.

Validation: 34/34 photon tests; E2E confirms config.yaml require_mention
+ custom mention_patterns bridge through load_gateway_config into a live
adapter and gate/strip correctly.
2026-06-08 13:38:30 -07:00
teknium1 d7f42e368e feat(photon): full channel parity — gateway setup, pairing, PII redaction, doc fixes
Brings Photon in line with how every other Hermes gateway channel
behaves, instead of being a one-off with its own surfaces.

- gateway setup: register a `setup_fn` so Photon appears in
  `hermes gateway setup` (the unified wizard) and runs the same
  device-login + project + user + sidecar flow as `hermes photon setup`.
  Adds `cli.gateway_setup()` as the zero-arg entry point.
- PII redaction: flip `pii_safe` False -> True. The comment already
  said iMessage E.164 numbers should be redacted; the value contradicted
  it. Now matches BlueBubbles (the other iMessage channel) which is in
  _PII_SAFE_PLATFORMS — phone numbers are stripped before reaching the LLM.
- Pairing/authz: already worked via the registry's allowed_users_env /
  allow_all_env generic path in authz_mixin; documented it. The adapter
  forwards unauthorized DMs to the gateway (no intake gating), so the
  pairing handshake fires and `hermes pairing approve photon <CODE>` works.
- Docs: fixed the `hermes photon status` output block to match the real
  labels (project key / webhook key, not project secret / webhook secret),
  added the missing PHOTON_API_HOST / PHOTON_DASHBOARD_HOST /
  PHOTON_HOME_CHANNEL_NAME env vars, and added gateway-setup +
  authorize-users sections mirroring the other channel docs.

Validation: 26/26 photon tests, 6504/6504 gateway+plugins tests, registry
E2E confirms setup_fn dispatch + pii_safe + authz envs all wired.
2026-06-08 13:38:30 -07:00
Teknium 5b4e431e8c feat(gateway): add Photon Spectrum (iMessage) platform plugin
First-class iMessage support via Photon's managed Spectrum platform.
Targeted as a successor to the BlueBubbles adapter — Photon allocates
the iMessage line, handles delivery, and abuse-prevention so users
don't have to run their own Mac relay. Free tier uses Photon's shared
line pool.

Architecture:
- Inbound: signed JSON webhooks (X-Spectrum-Signature, HMAC-SHA256)
  delivered to a local aiohttp listener. Dedupes on message.id,
  rejects deliveries with >5min timestamp drift.
- Outbound: small supervised Node sidecar that runs the spectrum-ts
  SDK. Photon does not currently expose a public HTTP send-message
  endpoint; the sidecar is the only way to call Space.send() today.
  When Photon ships an HTTP send endpoint we collapse the sidecar
  into _sidecar_send and drop the Node dep — every other layer of
  the plugin stays the same.
- Setup: 'hermes photon login' runs the RFC 8628 device-code flow;
  'hermes photon setup' creates a Spectrum-enabled project, creates
  a shared user (free tier), installs the sidecar's npm deps.
- Webhook management: 'hermes photon webhook register|list|delete'.
- Credentials persisted under credential_pool.photon /
  credential_pool.photon_project in ~/.hermes/auth.json.

Plugin path (not built-in) — per current policy (May 2026), all new
platforms ship under plugins/platforms/. Registers itself via
ctx.register_platform() + ctx.register_cli_command(), zero edits to
core gateway code.

Tests cover:
- HMAC-SHA256 signature verification (happy path, tampered body,
  wrong secret, drift, missing v0 prefix, empty inputs, non-integer
  timestamp)
- Inbound dispatch for text DMs, group ids (any;+;...), and
  attachment metadata markers
- Deduplication window
- check_requirements gating when Node is absent
- Device-code flow: request, header-based token return,
  body-fallback token return, access_denied propagation
- Project/user/webhook API clients with mocked httpx

Known limitations (current Photon API):
- Attachments are metadata only — no download URL yet
- Outbound attachment send not wired (sidecar can add easily)
- Reactions / message effects not exposed yet

Docs: website/docs/user-guide/messaging/photon.md + sidebar entry.
2026-06-08 13:38:30 -07:00
Teknium 74744795af
docs(tui): correct HERMES_TUI_GATEWAY_URL — dashboard-internal, not remote-attach (#42162)
The TUI docs presented HERMES_TUI_GATEWAY_URL + /api/ws as a supported
'attach the TUI to a standalone running gateway' workflow. It isn't.

/api/ws exists only inside the dashboard's FastAPI server
(hermes_cli/web_server.py), which spawns its own embedded TUI child and
injects the var as an internal wiring detail. The OpenAI-compat API
server (api_server platform) deliberately does not serve /api/ws, so the
documented ws://host:port/api/ws workflow 404s — the cause of #32882 and
the two PRs (#32904, #32955) that tried to add the route to the wrong
surface.

Rewrites the section in en + zh-Hans to describe the var accurately and
point users at shared state.db / dashboard embedded chat for multi-surface
session sharing.
2026-06-08 09:37:03 -07:00
yoniebans 2284147044 docs: document commits field on /api/hermes/update/check 2026-06-08 08:58:26 -07:00
Teknium 8e223b36ed
fix(curator): protect load-bearing built-in skills from archival/consolidation (#41817)
The curator's idle-archival path (apply_automatic_transitions under
prune_builtins) could archive the bundled `plan` skill, killing the
/plan slash command silently — typing /plan then returned 'Unknown
command' with no signal that a skill had vanished. The archived skill's
hash stays in .bundled_manifest, so 'hermes update' wouldn't re-seed it.

Add PROTECTED_BUILTIN_SKILLS ({plan}) enforced at the master gate
is_curation_eligible() (covers archive_skill + the transition walk) and
in the candidate enumerator (so the LLM consolidation pass never sees
them). Immune to prune_builtins, pin state, and LLM judgment.
2026-06-07 22:23:29 -07:00
Teknium 86c537d209
fix(memory): instruct in-turn consolidation + retry on overflow (#41755)
* fix(memory): make overflow errors instruct in-turn consolidation + retry

When bounded memory is full, the add/replace overflow errors now explicitly
tell the model to consolidate (merge/remove/shorten) and retry the write in
the same turn, matching the documented behavior. The replace-overflow path
now also echoes current_entries + usage for parity with add-overflow, so the
model has the same context to act on.

Closes #23378 (working-as-documented; this sharpens runtime to match docs).

* fix(memory): broaden overflow remediation hint beyond 'stale'

Say 'stale or less important' — entries don't have to be stale to be the
right ones to drop when making room.
2026-06-07 22:16:28 -07:00
Teknium ace4b722dc
feat(skills): add simplify-code skill — parallel 3-agent code review and cleanup (#41691)
Inspired by Claude Code's /simplify. A bundled skill that captures recent
changes via git diff, fans out three focused reviewers (reuse, quality,
efficiency) via delegate_task batch mode, then aggregates findings and
applies the fixes worth applying.

Zero core changes — orchestrates existing tools (terminal/git, search_files,
delegate_task). Supports focus, dry-run, and scoped-diff modifiers.

Closes #379.
2026-06-07 22:02:41 -07:00
Teknium d3b670e63e
docs(codex): document --sandbox danger-full-access for gateway bubblewrap failures (#40619)
Salvaged from #40435; re-verified on main, tightened, tested.

Co-authored-by: ziwon <ziwon@users.noreply.github.com>
2026-06-07 18:36:18 -07:00
Teknium ad399b9229
docs(update): document updates.* config keys (pre_update_backup, backup_keep, non_interactive_local_changes) (#40617)
Salvaged from #40540; re-verified on main, tightened, tested.

Co-authored-by: jiangkoumo <jiangkoumo@users.noreply.github.com>
2026-06-07 18:29:56 -07:00
Teknium 2aa316ec9c
docs(windows): fix Get-Command PATH guidance to venv\Scripts\hermes.exe (#40613)
Closes #40464.

Salvaged from #40488; re-verified on main, tightened, tested.

Co-authored-by: gauravsaxena1997 <gauravsaxena1997@users.noreply.github.com>
2026-06-07 18:28:23 -07:00
manishbyatroy 490c486ff6 fix(simplex): accept display name in SIMPLEX_ALLOWED_USERS
SIMPLEX_ALLOWED_USERS silently denied every contact when operators
listed display names instead of numeric contactIds. The SimpleX UI
never surfaces the numeric id, so display names are what operators
naturally put in the env var. _is_user_authorized only compared
source.user_id (the contactId), so the allowlist never matched.

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

Salvaged from PR #40393. Adds manishbyatroy to release.py AUTHOR_MAP.
2026-06-07 04:53:22 -07:00
Teknium 9e63109522
feat(dashboard): change UI font from the theme picker, independent of theme (#41145)
The dashboard font is now selectable from the UI, not just YAML. A new Font
section in the header theme picker overrides the UI font of whatever theme is
active; the choice is orthogonal to the theme and survives theme switches.
Each theme keeps its own font as the default — picking "Theme default" clears
the override.

- web/src/themes/fonts.ts: curated font catalog (system + Google Fonts across
  sans/serif/mono), each with a family stack and optional webfont URL. The
  catalog is the only injected-font surface — no free-text URL box, so the
  injected <link> origins stay fixed.
- web/src/themes/context.tsx: font-override state (localStorage + server),
  applied after theme typography so it wins; theme apply re-asserts it, and
  clearing re-runs theme apply to restore the theme's own font. Mono is left
  to the theme so code/terminal are untouched.
- web/src/components/ThemeSwitcher.tsx: Font section with grouped, self-
  previewing font rows and a "Theme default" clear option.
- hermes_cli/web_server.py: GET/PUT /api/dashboard/font persisting to
  config.yaml dashboard.font, with a server-side id allow-list (unknown ids
  coerce to the theme sentinel).
- i18n + types, api client methods, tests, and docs.

Validation: 6 new backend endpoint tests pass; tsc + vite build clean; live
browser test confirmed pick/persist/survive-theme-switch/clear all work.
2026-06-07 03:39:01 -07:00
Teknium 2d099fed1e
docs: deep audit — registry drift, stale claims, 2-week PR coverage, dashboard screenshot (#40952)
Full-corpus correctness audit of the hand-written docs against the codebase,
plus a 2-week merged-PR coverage sweep and one live dashboard screenshot.

Correctness (verified against COMMAND_REGISTRY / PROVIDER_REGISTRY / TOOLSETS /
tools.registry / DEFAULT_CONFIG / source):
- reference: add /version slash command, context_engine toolset, openai-api +
  novita-ai to --provider; fix tool count 64->71; model_catalog ttl 24->1;
  add profile describe to summary table; add real provider env vars
  (LM_API_KEY/LM_BASE_URL, KIMI_CODING_API_KEY, ALIBABA_CODING_PLAN_*,
  ANTHROPIC_BASE_URL, COPILOT_API_BASE_URL); fix faq "Windows: not natively".
- user-guide: fix broken `hermes -w -q` (->-z) and `hermes logs --tail` (->-f);
  language list 8->16; aux slots 8->11; docker separate-dashboard claim;
  _SECURITY_ARGS -> _BASE_SECURITY_ARGS.
- features: curator prune_builtins truth + missing CLI verbs; codex-runtime aux
  keys (context_compression->compression, vision_detect->vision); kanban
  terminate endpoint + promote/reassign/schedule/diagnostics/edit + per-profile
  cap; mcp mTLS (client_cert/client_key); built-in-plugins nemo_relay +
  teams_pipeline; api-server run approval endpoint; computer-use frontmatter.
- features N-Z + integrations: StepFun step-3-mini->step-3.5-flash; web-search
  backends 4->8; tool-gateway image-model IDs; voice-mode STT/TTS enums; remove
  phantom `rl` toolset; nous-portal status subcommand.
- messaging: WeCom typing/streaming cols; telegram transport default edit->auto;
  sms host default; simplex/ntfy `gateway setup` + pairing approve; line
  smart-chunking; matrix MATRIX_DM_AUTO_THREAD.
- developer-guide: build-a-plugin code examples (register_command signature,
  ContextEngine/ImageGenProvider/MemoryProvider ABCs); model-provider-plugin
  entry-point group hermes.plugins->hermes_agent.plugins; PLUGIN.yaml->plugin.yaml;
  agent-loop stale LOC; web-search-provider phantom crawl().

PR coverage (2-week window, 149 feat PRs):
- desktop.md refreshed for ~15 shipped features (zh-Hans switcher, rebindable
  shortcuts + zoom + Cmd+K, status-bar model picker + YOLO toggle, session-by-id
  + archive, multi-profile concurrent + cross-profile @session, composer history,
  Providers pane, per-profile remote hosts, Grok OAuth, aux-pin warning).
- configuration.md gateway-streaming default corrected to per-platform.
- tool-gateway.md free tool pool entitlement note.

Media:
- New /img/dashboard/admin-config.png — live dashboard Config admin page
  (captured from a clean profile, no secrets/personalization).
2026-06-07 01:39:06 -07:00
Gille fda66c488b docs(kanban): clarify decomposer profile roles 2026-06-06 19:29:00 -07:00
Gille fd4c8b404b
docs(signal): clarify tool progress support (#40774) 2026-06-06 18:54:33 -07:00
liuyuchen b2b4d97bbb docs: document update local-change handling 2026-06-06 18:36:06 -07:00
Teknium 5b43bf7d02
feat: uninstall the Chat GUI without removing the agent (CLI + desktop UI) (#40355)
* feat: uninstall the Chat GUI without removing the agent (CLI + desktop UI)

Adds a GUI-only uninstall path so people can remove the desktop Chat GUI
while keeping the Hermes agent + their config/sessions/.env, and surfaces
the three CLI uninstall modes inside the desktop app's Settings → About.

CLI:
- New hermes_cli/gui_uninstall.py: cross-platform discovery + removal of the
  desktop GUI's artifacts (source-built dist/release/node_modules + build
  stamp, the packaged app bundle, and the Electron userData dir) on Linux,
  macOS, and Windows. Never touches the agent source, venv, or user data.
- `hermes uninstall --gui` removes only the Chat GUI; `--gui-summary` prints a
  JSON install snapshot (used by the desktop UI to gate options + detect a
  missing agent for a future lite client).
- `hermes uninstall --yes` / `--full --yes` now run non-interactively, sharing
  the destructive sequence via a new _perform_uninstall() helper. The keep-data
  and full flows also sweep the GUI artifacts.

Desktop:
- electron/desktop-uninstall.cjs: pure helpers mapping each mode (gui/lite/full)
  to CLI flags, resolving the running app bundle per OS, and building the
  detached cleanup script that waits for the app to exit, runs the Python
  uninstall, and removes the bundle.
- IPC hermes:uninstall:summary / :run, preload bridge, and types.
- Settings → About "Danger zone" with the three options; agent-removing
  options hide when no local agent is detected.

Tests: tests/hermes_cli/test_gui_uninstall.py (22 pass with the existing
uninstall tests), electron/desktop-uninstall.test.cjs (17 pass, wired into
test:desktop:platforms). Docs: desktop.md "Uninstalling" + cli-commands.md.

* fix(desktop): tear down backend process tree before GUI uninstall (Windows lock safety)

The desktop uninstall cleanup script waited only on the desktop app's own
PID, but a backend grandchild (gateway / pty terminal / hermes REPL) can
outlive it and keep hermes.exe + venv files mandatory-locked on Windows —
making the script's rmdir half-fail and leaving a partial install, the same
failure class as the self-update path's #37532.

- main.cjs: runDesktopUninstall now awaits releaseBackendLock() before
  spawning the cleanup script — tree-kills every backend PID the desktop owns
  (primary + pool) via taskkill /T /F and polls the venv shim until unlocked.
  Extracted the shared core out of releaseBackendLockForUpdate so both the
  update hand-off and the uninstaller use the identical, incident-hardened
  teardown. No-op on macOS/Linux (no mandatory locks).
- desktop-uninstall.cjs: Windows cleanup script removes the bundle via a
  bounded rmdir retry loop (10x, 1s) instead of a single rmdir, since Windows
  releases directory handles lazily even after the holding process exits.
- Dropped a fragile tasklist|findstr reap-by-path attempt; the Electron-side
  tree-kill-by-PID is the reliable mechanism.

Tests: desktop-uninstall.test.cjs updated for the retry-loop output (17 pass).

* fix(desktop): address review on GUI uninstall (venv self-delete, gates, wait-loop)

Resolves @OutThisLife's review on #40355:

1. full mode now gated on agent presence (needsAgent: true). It removes the
   agent + user data, so on a lite client with no local agent it's hidden
   like lite — no more offering to remove an agent that isn't there.

2. (Finding 3, the real bug) lite/full no longer rmtree the venv from the
   venv's OWN python. On Windows a running python.exe is mandatory-locked, so
   that half-fails. New lightweight 'python -m hermes_cli.uninstall --mode X'
   entrypoint (stdlib-only imports) lets the desktop run agent-removing modes
   under the SYSTEM python (findSystemPython) with PYTHONPATH=<agentRoot>, so
   import hermes_cli resolves from source while the venv is torn down. Falls
   back to venv python + logs when no system python (gui-only unaffected).

3. Windows wait-loop is now bounded (60 tries, matching POSIX) and matches the
   PID as a whole space-delimited token via findstr (no substring 99->990
   trap, no redundant bare find). set HERMES_HOME/PID/PYTHONPATH now quoted.

4. Renamed the misleading 'returns null for dev run' test — the dev-run safety
   is shouldRemoveAppBundle(isPackaged=false), which the test now asserts.

Docs: note that --gui on a source checkout also sweeps node_modules/build
output. Tests: 18 python + 19 desktop pass.
2026-06-06 18:22:38 -07:00
helix4u 8b23b2bc01 docs: align runtime footer field docs 2026-06-06 11:20:40 -06:00
Teknium d41427504e
feat(delegation): uncap max_spawn_depth (floor 1, no ceiling) (#39772)
* fix: respect disabled auto-compaction on context overflow

Port from anomalyco/opencode#30749.

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

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

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

* feat(delegation): uncap max_spawn_depth to match max_concurrent_children

Removed the hard ceiling of 3 on delegation.max_spawn_depth. Depth now has
a floor of 1 and no upper limit, mirroring max_concurrent_children. Cost
(each level multiplies API spend) is the practical limiter, not a constant.

- delegate_tool.py: drop _MAX_SPAWN_DEPTH_CAP, _get_max_spawn_depth() floors
  at 1 instead of clamping to [1,3]; depth-limit error string reworded
- config.py / cli-config.yaml.example: doc comments say floor 1, no ceiling
- docs (configuration, delegation, delegation-patterns): range 1-3 -> >=1
- tests: convert clamp-above-3 change-detector into a no-ceiling invariant,
  drop the _MAX_SPAWN_DEPTH_CAP==3 snapshot assert, fix warning-text assert
2026-06-05 04:46:02 -07:00
Teknium 8a9ded5b21
feat(discord): voice-channel mixer — ambient idle bed + verbal acks that overlap TTS (#39659)
* feat(discord): voice-channel mixer — ambient idle bed + verbal acks that overlap TTS

Discord voice mode can now feel conversational: the bot speaks a short
acknowledgement before it starts working, and a subtle ambient 'thinking' bed
plays underneath while tools run, ducking under speech and swelling back — the
Grok-voice-mode feel.

discord.py plays only one audio stream per voice connection, so this adds a
software mixer (VoiceMixer, a discord.AudioSource) installed once per guild on
join. It sums an ambient loop, verbal acks, and TTS replies into that single
20ms/48kHz/stereo stream (numpy int16 add + clip), so they overlap instead of
stop-and-swap. Speech ducks the ambient gain down and releases it smoothly.

- plugins/platforms/discord/voice_mixer.py: VoiceMixer + MixerChild (gain,
  loop, fade, duck/release), decode_to_pcm (ffmpeg), synth_ambient_pcm (no
  asset needed — synthesised pad).
- adapter: install mixer on join, tear down on leave, route
  play_in_voice_channel through the mixer (legacy one-shot path kept as
  fallback), play_ack_in_voice, voice_mixer_active. Defensive getattr for the
  object.__new__ test helpers.
- gateway/run.py: tool_start_callback fires a one-time verbal ack on the first
  tool call of a turn when in a voice channel (independent of the text
  tool-progress gate). No system-prompt or message-flow changes.
- config: discord.voice_fx.* (OFF by default; ambient/duck/speech gains, ack
  phrases). All in config.yaml, not .env.
- docs + tests (mixer unit + adapter integration).

Verified: 19 new tests pass, existing voice suite green (2 pre-existing
davey-module env failures unchanged), and a real-mixer E2E confirms ambient
streams, TTS overlaps it, acks layer in, and teardown is clean.

* fix(discord): make voice mixer numpy import lazy (numpy is voice-extra-only)

numpy ships in the optional 'voice' extra, not [all,dev], so a module-level
'import numpy' broke CI test collection (and would break the always-imported
Discord adapter on any install without the voice extra). Defer numpy to the
functions that actually mix audio via _require_numpy(); guard the test module
with pytest.importorskip('numpy').
2026-06-05 03:10:40 -07:00
Ben Barclay 9ab9c923da
docs(dashboard): clarify auth provider suitability + registration across dashboard/Docker/Desktop docs (#39633)
* docs(dashboard): clarify auth provider suitability + document dashboard registration

- Add a 'Registering a dashboard' subsection under the Nous Research
  provider covering both the 'hermes dashboard register' CLI command
  and the Portal /local-dashboards GUI page.
- Note that the Nous provider is the one suitable for public-internet
  exposure (logins verified against your Nous account).
- Add a warning that the username/password provider is for trusted
  networks / VPN only and is not suitable for direct public-internet
  exposure; point readers to the Nous / OIDC / custom OAuth providers.
- Surface the same distinction in the two-provider intro list.

* docs(dashboard): count three bundled auth providers, add self-hosted OIDC to intro

'Two providers ship in the box' undercounted — the bundled
plugins/dashboard_auth/self_hosted (generic OpenID Connect) is a third.
List all three in the gated-mode intro and link each to its section.

* docs(dashboard): extend auth provider updates to Docker and Desktop pages

- docker.md: list all three bundled gate providers (was username/password
  + OAuth only), adding the self-hosted OIDC provider and its env vars,
  and note username/password is not for public-internet exposure.
- desktop.md: reframe the remote-backend connection so OAuth (Nous Portal)
  is the preferred option for any backend reachable beyond the local
  machine, with username/password positioned for local / trusted-network
  use only. Cover the 'Sign in with <provider>' OAuth flow in the in-app
  steps and scope the VPN warning to the password path.

* docs(dashboard): align env-var, CLI, and remote-Desktop recipe with provider changes

- environment-variables.md: reframe the Web Dashboard & Hermes Desktop
  intro (OAuth preferred for remote/public, username/password for
  trusted networks), add the self-hosted OIDC env vars
  (HERMES_DASHBOARD_OIDC_*) that were missing from the table, and note
  hermes dashboard register provisions the OAuth client_id.
- cli-commands.md: document the 'hermes dashboard register' subcommand
  (flags, behavior, /local-dashboards GUI alternative).
- web-dashboard.md: apply the OAuth-preferred reframe to the bottom
  'Connecting Hermes Desktop to a remote backend' recipe and scope its
  VPN warning to the username/password path, matching desktop.md.

* docs(dashboard): move 'recommended remote Desktop path' framing from username/password to OAuth

The gated-mode intro list claimed the username/password provider was the
recommended path for a remote Hermes Desktop connection, contradicting the
OAuth-preferred framing established elsewhere. Move that recommendation onto
the OAuth (Nous Portal) item so the docs are consistent: OAuth is the
recommended provider for any remote/internet-facing backend; username/password
is for trusted networks only.

* docs(dashboard): drop unreleased managed/hosted-install provisioning notes

Remove the 'not available in managed/hosted installs, where the client id is
provisioned by the hosting platform' line from the dashboard register docs
(web-dashboard.md, cli-commands.md) and the 'provisioned by the Nous Portal for
hosted deploys' clause from the HERMES_DASHBOARD_OAUTH_CLIENT_ID env-var row —
that platform-provisioning path is unreleased.

* docs(dashboard): drop --portal-url / HERMES_DASHBOARD_PORTAL_URL from user docs

The portal-URL override targets a non-production Nous Portal and only works
for internal Nous usage — it won't function for end users (the access token
must be issued by the same portal). Remove it from the register CLI flags,
the Nous-provider config/env tables, and the verify-the-gate example so users
aren't pointed at an option that can't work for them.

* docs(dashboard): add worked examples for Nous and username/password providers

The self-hosted OIDC provider already had a full 'Worked example: Keycloak'
walkthrough; the Nous and username/password providers only had scattered
config snippets. Add parallel '#### Worked example' sections for both
(register/run/login + /api/status verification), mirroring the Keycloak
example's structure so all three bundled providers read consistently.

* docs(env): move HERMES_DESKTOP_REMOTE_URL to end of the dashboard auth table

It was sitting between the HERMES_DASHBOARD_BASIC_AUTH_* block and the
HERMES_DASHBOARD_OAUTH/OIDC block, splitting the dashboard-side vars. As the
only desktop-side var in the table, it belongs at the end so the dashboard
provider vars (basic, OAuth, OIDC) stay grouped together.

* docs(dashboard): remove Fly.io references from dashboard auth docs

Fly.io is the internal hosting implementation for hosted Hermes — it shouldn't
leak into user-facing dashboard auth docs. Reword the OAuth provider intro,
the env-var-path rationale, the public-URL-override section, the cookie Secure
note, and the verify-the-gate example to generic 'hosting platform' / 'reverse
proxy' / 'TLS terminator' phrasing.

Left the legitimate user-facing Fly.io mentions in telegram.md (a deliberate
cloud-deployment walkthrough) and work-with-skills.md (a generic example)
untouched.
2026-06-05 18:34:19 +10:00
ethernet 80672754a8 fix(docs): update all install instructions everywhere 2026-06-04 21:07:45 -04:00
bedirhancode 99cee124dc
docs(install): warn that VPS browser consoles mangle special chars (#36279) (#38811)
Some VPS providers (Hetzner Cloud and others) offer a browser-based
console for managing hosts. These consoles transmit special characters
incorrectly — ':' may arrive as ';', '@' may be mis-rendered, and
non-English keyboard layouts fare worse — which silently corrupts
'docker run' arguments like '-v ~/.hermes:/opt/data', '-e KEY=value',
and pasted API keys / tokens.

Adds a :::caution admonition above the Quick start 'docker run' block
in website/docs/user-guide/docker.md recommending SSH for copy-paste-
safe command entry, with manual-typing guidance as a fallback.

Pure docs change, no code touched.

Closes #36279

Co-authored-by: Bedirhan Celayir <bedirhancode@users.noreply.github.com>
2026-06-05 10:49:55 +10:00
Teknium b20fcffa54
docs: make dashboard/gateway prerequisites explicit for remote-backend connection (#39128)
Both the desktop and web-dashboard remote-backend sections now state up front
that the 'remote backend' is a running 'hermes dashboard' process the desktop
app attaches to (it does not start it for you), and that the gateway is a
separate process needed only for messaging channels.
2026-06-04 17:38:49 -07:00
Teknium 5300727a08
revert: keep Google Chat OAuth secret + active_provider profile-scoped (#39398)
* Revert "fix(gateway): anchor Google Chat OAuth client secret to default Hermes root"

This reverts commit fff0561441.

* Revert "fix(cli): honor global-root active_provider fallback for named profiles"

This reverts commit 3858cf4307.

* docs(google_chat): describe OAuth client secret as profile-scoped, not host-wide

The setup docs, oauth docstring, and the adapter's 'no credentials'
error message all described the Google Chat OAuth client secret as
host-wide shared infrastructure. That contradicts profile isolation:
profiles are separate auth boundaries, so two profiles can point at
different Google OAuth apps / accounts. Reword all three to say the
secret is profile-scoped and each profile registers its own.
2026-06-04 16:54:40 -07:00
Frowtek fff0561441 fix(gateway): anchor Google Chat OAuth client secret to default Hermes root 2026-06-04 06:45:32 -07:00
zhaolei.vc f3bbfda6d1 feat(gateway): handle Feishu meeting invitations
Change-Id: I8cf5638393dd9adb1d7be5e170ce5082b41f77fa
2026-06-04 06:15:23 -07:00
Teknium 38d3c49aaf
refactor(skills): clean up bundled skill set + add environments: relevance gate (#39028)
* refactor(skills): clean up bundled skill set + add environments: relevance gate

Bundled skills cleanup pass plus a new offer-time relevance gate.

Removals (redundant / dead):
- spotify (covered by the spotify plugin's 7 native tools)
- linear (covered by `hermes mcp install linear`)
- kanban-codex-lane, debugging-hermes-tui-commands
- empty category markers: diagramming, gifs, inference-sh,
  mlops/training, mlops/vector-databases
- domain (stale orphan dup of optional/research/domain-intel)

Bundled -> optional:
- baoyu-article-illustrator, baoyu-comic, creative-ideation, pixel-art
- dspy, subagent-driven-development
- minecraft-modpack-server, pokemon-player
- hermes-s6-container-supervision (-> optional/devops)

Consolidation:
- webhook-subscriptions + native-mcp folded into the hermes-agent skill
  as references/webhooks.md + references/native-mcp.md with SKILL.md pointers
- writing-plans merged into plan (v2.0.0); related_skills + prose refs updated

New: environments: frontmatter gate (agent/skill_utils.skill_matches_environment)
- Offer-time relevance filter (kanban / docker / s6), parallel to platforms:.
- Wired into the 3 OFFER surfaces only (prompt_builder skills index,
  skills_tool.list_skills, skill_commands slash discovery).
- Explicit loads (skill_view, --skills preload) intentionally BYPASS it, so
  load-bearing force-loads like the kanban dispatcher's `--skills kanban-worker`
  always resolve. Verified via E2E.
- kanban-orchestrator/kanban-worker tagged environments: [kanban];
  hermes-s6-container-supervision tagged environments: [s6] + platforms: [linux].

Validation: 8/8 E2E gating assertions (incl force-load invariant);
442 targeted tests green (agent, skills_tool, skill_commands, kanban worker).

* docs: regenerate skill catalogs + pages for the bundled cleanup

Regenerated per-skill doc pages, catalogs, and sidebar to match the skill
moves/removals in the parent commit. Moved skills' pages relocate
bundled -> optional (history preserved); removed skills' pages deleted;
edited skills' pages refreshed (hermes-agent now embeds the webhook +
native-mcp reference pointers). zh-Hans i18n mirror: stale bundled pages
and catalog rows for moved/removed skills pruned (new optional translations
land via the translation pipeline).

* test: drop regression test for removed kanban-codex-lane skill

The kanban-codex-lane skill was removed in the bundled-skills cleanup;
its dedicated regression test read the now-deleted SKILL.md and failed
with FileNotFoundError on CI shard 6.
2026-06-04 06:11:22 -07:00
Teknium d12c233378
docs(wecom): stop implying live streaming and typing support (#38990)
The WeCom adapter delivers each response as a single complete message
via aibot_respond_msg / aibot_send_msg — it does not stream tokens
incrementally (no edit_message override) and send_typing is a no-op.
Reword the 'Reply-mode streaming' feature bullet to 'Reply correlation',
retitle the section to 'Reply-Mode Responses', and add a note clarifying
that neither token streaming nor typing indicators are supported.
2026-06-04 05:57:01 -07:00
Teknium c0435f4fef
docs: remote desktop connect uses username/password, not --insecure + session token (#38926)
The documented path for connecting Hermes Desktop to a remote backend was
`--insecure` + a pinned HERMES_DASHBOARD_SESSION_TOKEN — an unauthenticated
bind plus a copy-pasted token. Replace it everywhere with the bundled
username/password dashboard-auth provider: set HERMES_DASHBOARD_BASIC_AUTH_*,
run `hermes dashboard --host 0.0.0.0` (the non-loopback bind engages the auth
gate), and Sign in from the app.

- desktop.md: rewrite 'Connecting to a remote backend' for the user/pass + Sign in flow
- web-dashboard.md: rewrite both remote-backend sections (overview + dedicated);
  reframe the auth-gate section so --insecure is a discouraged escape hatch, not a
  co-equal use case; drop the removed --tui flag from the systemd example
- environment-variables.md: lead with HERMES_DASHBOARD_BASIC_AUTH_*; drop the
  session-token / HERMES_DESKTOP_REMOTE_TOKEN remote-connect entries
- docker.md: mention the username/password provider as the simplest gate provider
2026-06-04 21:23:59 +10:00
Ben f57ce341dc feat(dashboard-auth): add generic self-hosted OIDC provider
Adds a bundled dashboard-auth provider plugin that authenticates the
web dashboard against any conformant self-hosted OpenID Connect server
(Authentik, Keycloak, Zitadel, Authelia, Auth0, Okta, Google, …) using
standard OIDC — no per-IDP code.

It's a pure drop-in plugin implementing the DashboardAuthProvider
protocol; it touches no core auth/runtime/login paths. Mechanics:

- OIDC discovery from {issuer}/.well-known/openid-configuration
  (cached; issuer pinned; endpoints required HTTPS, loopback http
  allowed for local-dev IDPs)
- authorization-code + PKCE (S256), public client
- verifies the OIDC ID token (RS256/ES256) against the discovered
  jwks_uri with iss/aud pinned to the configured issuer/client_id, and
  maps standard claims (sub/email/name/preferred_username, groups→org)
  onto a Session
- standard refresh_token grant for silent re-auth; RFC 7009 revocation
  on logout when advertised

Verifies the ID token (not the access token) because OIDC guarantees the
ID token is a signed JWT carrying identity, while access-token format is
opaque to the client per spec — the only universally-correct choice
across self-hosted IDPs.

Config via dashboard.oauth.self_hosted.{issuer,client_id,scopes} in
config.yaml or HERMES_DASHBOARD_OIDC_{ISSUER,CLIENT_ID,SCOPES} env vars
(env-wins-config, empty-is-unset — same convention as the nous plugin).
Confidential clients (client_secret) left as a documented TODO seam.

Docs: adds a Self-hosted OIDC section to the web-dashboard guide,
including a copy-paste Keycloak worked example (realm import + docker
run + dashboard wiring + login walkthrough).

Tests: 65 cases covering construction, discovery (incl. issuer
mismatch + https enforcement), start_login/PKCE, complete_login, ID
token verification, refresh/revoke, and env/config precedence.
2026-06-04 03:23:45 -07:00
Ben cae6b5486f feat(dashboard): always enable embedded chat; remove dashboard --tui flag
The dashboard's embedded Chat surface (/chat, /api/ws, /api/pty) was gated
behind `hermes dashboard --tui` / HERMES_DASHBOARD_TUI=1. The desktop app and
the dashboard's own Chat tab both drive the agent over the /api/ws + /api/pty
WebSockets, so a dashboard started without the flag would pass the /api/status
health check but slam the chat WebSocket shut with WS code 4403 — the app
connects, reports "ready", and chat stays dead. This was the root cause behind
multiple user reports of the desktop app failing to connect to a self-hosted
gateway/dashboard, and it bit Docker and host installs alike.

Make the embedded chat unconditional:

- web_server.py: _DASHBOARD_EMBEDDED_CHAT_ENABLED defaults to True; drop the
  embedded_chat parameter and the runtime reassignment from start_server().
  The WS gates still read the constant (now always true) so the seam — and its
  "rejects when disabled" contract test — stays meaningful.
- main.py: remove the `--tui` argument from the dashboard subparser and the
  `embedded_chat = args.tui or HERMES_DASHBOARD_TUI==1` derivation.
- web/: isDashboardEmbeddedChatEnabled() returns true unconditionally; drop the
  deprecated __HERMES_DASHBOARD_TUI__ alias and the dead LEGACY_TUI_RE scrape in
  the vite dev-token plugin.
- apps/desktop/electron/main.cjs: drop `--tui` from the spawned dashboardArgs
  (it would now error with "unrecognized arguments: --tui") and the redundant
  HERMES_DASHBOARD_TUI env injection.
- Docker: no s6 run-script change needed — the script never passed --tui; the
  HERMES_DASHBOARD_TUI env var is now simply a no-op, so the image works out of
  the box with no extra var.
- Docs: remove every dashboard --tui / HERMES_DASHBOARD_TUI reference across the
  CLI reference, env-var reference, docker/desktop/web-dashboard guides, in-app
  tips, and the zh-Hans translations. The terminal `hermes --tui` / HERMES_TUI
  references are intentionally left untouched.

Tests: 270 passing across web_server, dashboard lifecycle, host-header,
auth-gate, and docker-override-scripts suites.
2026-06-04 03:03:35 -07:00
Ben b36a30db20 docs(dashboard-auth): document the username/password provider
Add a 'Username/password provider (no OAuth IDP)' section to the web
dashboard guide (config.yaml + env surfaces, the explicit-secret caveat,
the rate-limit/generic-401 properties, and a 'write your own password
provider' pointer to the supports_password extension point), and list the
HERMES_DASHBOARD_BASIC_AUTH_* env vars in the environment-variables
reference.
2026-06-04 01:02:25 -07:00
kshitij 0401176c7a
Merge pull request #38760 from helix4u/fix/prefill-config-compat
fix(config): align prefill messages key handling
2026-06-03 23:52:47 -07:00
Siddharth Balyan f31c950182
refactor(supermemory): session-level ingest + kebab aliases (salvaged from #32487) (#38756)
* refactor(supermemory): session-level conversation ingest + kebab tool aliases

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

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

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

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

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

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

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

---------

Co-authored-by: Mahesh Sanikommu <maheshthedev@gmail.com>
2026-06-04 11:50:02 +05:30
helix4u ffb53767bf fix(config): align prefill messages key handling 2026-06-03 23:51:44 -06:00
Ben Barclay 04d620d91f
fix(docker): run config migrations during container boot (salvage #35508) (#36627)
Salvage of #35508 (@dchenk), rebased onto current main. Resolved the
tests/tools/test_stage2_hook_puid_pgid.py conflict (kept both the
envdir-creation regression test on main and the new config-migration
tests).

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

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

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

Closes #35508. Fixes #35406.

Co-authored-by: Dmitriy Cherchenko <17372886+dchenk@users.noreply.github.com>
2026-06-04 11:11:27 +10:00
Teknium ec69c767ff
docs(desktop): point Chat section to remote-backend + dashboard doc (#38545)
The Desktop Chat section described chat-only and gave no signpost that
remote-hosted Hermes connection is documented. Adds a pointer to the
in-page remote-backend section and to the deeper Web Dashboard doc.
2026-06-03 16:40:47 -07:00
Teknium 63727f32bf
docs(dashboard): document connecting Hermes Desktop to a remote backend (#38534)
Desktop's readiness probe only checks GET /api/status (public), but the
live chat rides /api/ws, which is gated by --tui (4403), a matching
session token (4401), and a non-loopback bind. The web-dashboard doc
covered --tui and the OAuth gate but never the Desktop remote-connection
flow, so the three independent failure modes weren't documented together.

Adds a 'Connecting Hermes Desktop to a remote backend' section: pin
HERMES_DASHBOARD_SESSION_TOKEN, run with --host 0.0.0.0 --insecure --tui,
the curl token-verification one-liner, and WS close-code triage.
2026-06-03 16:28:01 -07:00
helix4u 51a2c07016 fix(skills): document xurl X Article ingestion 2026-06-03 15:11:57 -07:00
kshitijk4poor da4f407e51 feat(cli): make `hermes portal` the human-readable Portal onboarding alias
`hermes portal` (no subcommand) now runs the one-shot Nous Portal onboarding
— OAuth login, switch provider to Nous, offer Tool Gateway — identical to
`hermes setup --portal` and the human-readable alias for
`hermes auth add nous --type oauth` (which still works).

The prior status default moves to `hermes portal info`; `status` is kept as a
hidden back-compat alias. `open`/`tools` subcommands are unchanged.

User-facing hints and docs (status.py, conversation_loop 401 guidance,
SystemPage, README, website docs + zh-Hans) now point at `hermes portal` /
`hermes portal info`. `--manual-paste` references keep the explicit auth
command since `hermes portal` does not expose that flag.
2026-06-04 01:19:28 +05:30
Teknium 3c73d1852e
docs: remote desktop connect needs --tui on the backend (#38350)
The Desktop App and Web Dashboard remote-connect instructions told users
to start the backend with `hermes dashboard --no-open --insecure --host
0.0.0.0`, omitting --tui. Without --tui the embedded-chat WebSockets
(/api/ws, /api/pty) are refused, so the desktop passes the /api/status
health check and reports the backend "ready" — but chat never works
because the socket is closed on connect.

- Add --tui to both backend command blocks (with an inline why-comment).
- Explain that the desktop chat runs over /api/ws + /api/pty and needs
  the embedded-chat surface enabled; a plain dashboard/gateway is not
  enough.
- Add a troubleshooting entry for the exact symptom (connects, says
  ready, chat dead) on both pages.
2026-06-03 09:30:20 -07:00
Bryan Bednarski 0d9b7132ff feat(observability): observer-grade telemetry hooks + NeMo-Relay plugin
Adds backend-neutral observer hooks for plugins: session, turn, API
request, tool, approval, and subagent lifecycle events with stable
correlation IDs (session_id, task_id, turn_id, api_request_id,
tool_call_id, parent/child subagent ids). Extends VALID_HOOKS with
api_request_error and subagent_start.

Hot path is zero-cost when no plugin subscribes: has_hook()/presence
checks gate all payload construction, request payloads are returned
by reference when no middleware rewrites, and the sanitized response
payload no longer embeds raw response objects.

Bundles the optional NeMo-Relay observability plugin
(plugins/observability/nemo_relay) as an in-repo consumer of the new
hooks, peer to the existing langfuse plugin. Fails open when the
optional nemo-relay package is not installed.

Authored-by: Bryan Bednarski <bbednarski@nvidia.com>
Salvaged from #29722 onto current main.
2026-06-03 06:36:46 -07:00
Teknium c5d199eada
feat(dashboard): check-before-update flow on the System page (#38205)
The dashboard's update button ran 'hermes update' immediately with no
preview. Now the System page shows whether an update is available and
asks the user to confirm before applying it.

- New GET /api/hermes/update/check: reports install method, current
  version, and commits-behind (via banner.check_for_updates, 6h-cached;
  ?force=1 busts the cache). Soft-fails to behind=null on network error;
  marks docker/nix/homebrew as can_apply=false with the out-of-band cmd.
- System page: update-status badge on the Hermes version row (latest /
  N behind), a Check-for-updates button, and an Update-now button that
  opens a ConfirmDialog showing the commit count before POST /api/hermes/
  update fires. Cached status loads with the rest of the page.
- Docs + 5 endpoint tests (git/up-to-date/docker/soft-failure + auth gate).
2026-06-03 05:57:15 -07:00
Teknium ef65298103
docs: make the Desktop App remote-backend section self-contained (#38194)
The section explained why the Session token is hidden but punted the actual
setup steps to the web-dashboard page via a link — a bounce for someone on
the Desktop App page trying to connect. Inline the concrete steps instead:
backend command block (mint token -> .env -> hermes dashboard --insecure),
the in-app Remote gateway steps, the env-var override, Tailscale guidance,
and a troubleshooting list. Keep a short pointer to the web-dashboard page
for the same setup from that angle.
2026-06-03 05:27:38 -07:00
Teknium d833b1eff7
docs: add remote-backend section to the Desktop App page (#38180)
The Desktop App page covered install, settings, and chat but not how to
connect the app to a backend on another machine — the exact thing
@PedjaDrazic asked about. Add a 'Connecting to a remote backend' section
that explains the Session token is the dashboard token Hermes never
surfaces (pin it via HERMES_DASHBOARD_SESSION_TOKEN + run --insecure),
and link to the web-dashboard page for the full backend setup rather than
duplicating it. Add a reciprocal link from the web-dashboard remote section
back to the Desktop App page.
2026-06-03 04:59:04 -07:00