Commit Graph

28271 Commits

Author SHA1 Message Date
kshitijk4poor 5eb108f06c chore: AUTHOR_MAP — yashiel@skyner.co.za → yashiels
PR #53284 salvage (discord markdown table-to-bullet conversion; #21168)
2026-06-27 03:38:29 +05:30
Teknium 391090083c fix(desktop): persist MoA preset add/delete/set-default immediately (#53290)
The desktop MoA settings 'Add preset', 'Set default', and 'Delete' buttons
mutated local React state only and never called the save endpoint, so a newly
constructed preset vanished on refresh. Each now builds the next config and
calls saveMoa() so the change is written to config.yaml via PUT /api/model/moa.
2026-06-26 15:06:18 -07:00
Teknium 7e101e553b fix(moa): block the moa virtual provider as a reference or aggregator slot (#53281)
A MoA preset whose reference or aggregator slot points at the moa virtual
provider creates a recursive MoA tree. The runtime guards in moa_loop.py only
surface this mid-turn (references silently skipped, aggregator raises). Reject
it at the config chokepoint (_clean_slot) so it can never be saved, and hide it
from the desktop/dashboard slot pickers so it isn't offered as a dead choice.
2026-06-26 14:42:42 -07:00
liuhao1024 515192c4b9 fix(tools): use start_new_session instead of preexec_fn to prevent SIGSEGV in multi-threaded processes
preexec_fn=os.setsid runs Python code in the forked child before exec,
which is unsafe in multi-threaded processes (CPython docs). When the
Desktop gateway loads native libraries (onnxruntime, BLAS, provider SDKs)
with active thread pools, the fork can SIGSEGV before the child execs.

Replace all preexec_fn usage with start_new_session=True, which provides
the same setsid/process-group semantics without running Python in the
fork. This is already the pattern used throughout hermes_cli/gateway.py
and hermes_cli/_subprocess_compat.py.

Fixes #46789
2026-06-27 03:08:41 +05:30
srojk34 f0678b031e fix(moa): tolerate non-numeric values in hand-edited MoA preset config
_normalize_preset uses bare float() and int() to coerce
reference_temperature, aggregator_temperature, and max_tokens from
config.yaml.  When a user hand-edits a non-numeric value (e.g.
max_tokens: "8k" or reference_temperature: "hot"), the coercion raises
ValueError.  Since normalize_moa_config runs on every model-selection
and MoA turn (via resolve_moa_preset), the crash is unrecoverable and
blocks all MoA usage until the config is manually fixed.

Replace the bare casts with _coerce_float / _coerce_int helpers that
fall back to the default on TypeError/ValueError instead of raising.
2026-06-26 14:35:38 -07:00
Teknium 9b2af36d5a docs(moa): document prompt-caching behavior for references and aggregator (#53218)
* docs(moa): document prompt-caching behavior for references and aggregator

* docs(moa): clarify references preserve cache, only aggregator trades reuse

* docs(moa): correct caching prose — tail-append preserves aggregator cache too
2026-06-26 12:58:05 -07:00
Teknium 525e1e775d fix(skills): background review fork respects pinned skills (#53226)
The autonomous self-improvement review fork could still write to a pinned
skill — only external/bundled/hub-installed/protected-builtin skills were
guarded. The curator skips pinned skills from every auto-transition; the
review fork is the same kind of no-user-present actor and must too.

Adds a pin check to _background_review_write_guard so background-origin
edit/patch/delete/write_file/remove_file on a pinned skill are refused.
Stricter than the foreground _pinned_guard (delete-only) by design: with
no user in the loop there is no one to consent to an edit.

Fixes #25839
2026-06-26 12:49:33 -07:00
Nacho Avecilla f509f6e598 fix(dashboard): offload PTY spawn/close off the event loop (#53227)
* Fix blocking tasks on the dashboard

* Remove unnecessary comments
2026-06-26 12:47:23 -07:00
Teknium 217047de2d fix(agent): silence verification-stop loop status line (#53223)
The verify-on-stop guard (#52296) printed '↻ Verification required before
finishing' to the terminal on every internal nudge turn, adding noise to
CLI/gateway sessions whenever code was edited without fresh passing checks.
Demote the user-facing status emit to a logger.debug breadcrumb — the loop
still nudges the model to verify before finishing, just silently.
2026-06-26 11:52:11 -07:00
briandevans 3c8d3ecfa0 fix(approval): extend gateway-lifecycle guard to launchctl and pidof-based kills
The dangerous-command approval layer already blocks `hermes gateway
(stop|restart)`, `pkill/killall hermes|gateway`, and `kill ... $(pgrep ...)`.
A reporter noted on #33071 that the agent can still achieve the same
effect by driving launchd directly against the gateway's service label
(`launchctl stop ai.hermes.gateway`, `launchctl kickstart -k
system/ai.hermes.gateway`, etc.) or by substituting `pidof` for `pgrep`
in the kill-expansion form.

This widens the "Gateway lifecycle protection" block in
`tools/approval.py` to cover both vectors:

- `launchctl (stop|kickstart|bootout|unload|kill|disable|remove)`
  scoped to commands that target a Hermes label (`hermes`,
  `ai.hermes`). Read-only inspection (`launchctl print …`,
  `launchctl list`) and operations against unrelated labels remain
  unflagged.
- `kill ... $(pidof …)` and the backtick form, alongside the existing
  `pgrep` expansion. `pidof` is the BSD/Linux equivalent and is
  equally opaque to the `(pkill|killall) … hermes` name pattern.

Intentionally left out of scope: plain `kill -TERM <numeric_pid>` with
a PID looked up out-of-band. Catching that would require runtime PID
state and would break the existing
`TestPgrepKillExpansion::test_safe_kill_pid_not_flagged` contract,
which guarantees that a plain literal-PID `kill 12345` stays safe.
2026-06-26 11:38:28 -07:00
ethernet ba7026c376 feat(docs): clarify termux/nix as t2 platforoms 2026-06-26 11:37:56 -07:00
ethernet 772cf847b0 feat(docs): clarify platform support 2026-06-26 11:37:56 -07:00
ethernet 699adc2ca5 regen package-lock for integrity checks 2026-06-26 11:37:56 -07:00
brooklyn! ed962104c8 Merge pull request #52935 from NousResearch/bb/desktop-inline-rendering
feat(desktop): inline rich embeds, diagrams & alerts in assistant markdown
2026-06-26 13:36:43 -05:00
Brooklyn Nicholson db6ced4712 feat(desktop): consent gate for inline embeds (per-embed / per-service)
Embeds reach out to third parties on render, so default to a placeholder that
mirrors the tool-approval UX: "Load <service>" (this embed) or "Always allow
<service>" (persisted). A desktop-local store ($embedMode ask|always|off +
per-service allowlist) gates the fetch with zero gateway round-trip; an
Appearance setting controls the global default. Local renderers (mermaid, svg,
alerts) are never gated. Addresses review feedback on outbound third-party
requests.
2026-06-26 13:35:01 -05:00
Teknium 2d3071f9d4 docs(moa): clarify MoA presets are selectable on every surface (CLI, hermes model, Dashboard, Desktop, TUI) (#53211) 2026-06-26 11:16:14 -07:00
Teknium 9dd56f0dfb docs(moa): add HermesBench results to Mixture of Agents page (#53206) 2026-06-26 11:05:07 -07:00
Teknium 3d735fe156 fix(skills-hub): surface per-tap providers (NVIDIA/OpenAI/...) in runtime search (#53191)
Natural-language skill search returned a short, arbitrary list and never
surfaced NVIDIA (or OpenAI/Anthropic/HuggingFace) skills. Two causes:

1. The runtime index collapses every GitHub tap into source="github", so
   there was no way to find or filter by provider at the CLI — the per-tap
   identity only existed in the docs-site catalog.
2. HermesIndexSource.search matched only name/description/tags (not the
   identifier or provider) and broke at the first `limit` hits in raw index
   order, burying the most relevant skills. `search` also defaulted to
   --limit 10 against an 86k-entry catalog.

Changes:
- GitHubSource stamps a per-tap provider label (extra.provider) on each
  skill via github_provider_for(); source stays "github" so dedup/floor/
  index-skip logic is untouched. Flows into the built index.
- HermesIndexSource.search now matches identifier + provider too, and
  collect-then-ranks (exact > prefix > whole-word > substring) instead of
  break-at-limit.
- --source nvidia|openai|anthropic|huggingface|voltagent|gstack|minimax
  provider filters for browse/search (narrows merged results by provider).
- search --limit default 10 -> 25; table Source column shows the provider
  label for github skills.

Tested: 181 unit tests pass; E2E against the live runtime index confirms
'nvidia'/'cuda' searches now surface NVIDIA-provider skills and
--source nvidia narrows to exactly the NVIDIA catalog.
2026-06-26 11:04:41 -07:00
Teknium d430684d7c fix(gateway,windows): respawn gateway windowless after GUI update (#52239)
The post-update gateway restart path relaunched the gateway with the
venv's console `python.exe` (via `get_python_path()` in
`_gateway_run_args_for_profile`). On Windows this leaves a terminal
window open permanently: uv's `venv\Scripts\python.exe` is a launcher
shim that re-execs the *base* console interpreter, which allocates its
own conhost — and `CREATE_NO_WINDOW` cannot suppress that second window.
The clean-start path (`_spawn_detached`) already dodges this by routing
through `_resolve_detached_python` to use the windowless base
`pythonw.exe`; the restart watcher did not.

Symptom (reported on Windows 11): after an in-app GUI update, a console
window for the gateway stays open and never closes. Confirmed on the
reporter's box — the running gateway was `python.exe ... gateway run
--replace` with a live conhost child and the foreground "Press Ctrl+C to
stop" banner, born exactly at the update's "Restarting Windows gateway"
log line.

Fix:
- Add `gateway_windows.windowless_gateway_restart_spec(run_argv)` which
  rewrites a console-python gateway argv into the windowless `pythonw.exe`
  equivalent and returns the cwd + env overlay (VIRTUAL_ENV / PYTHONPATH /
  HERMES_HOME) the base interpreter needs to import `hermes_cli` without
  the venv launcher's site config. No-op on POSIX.
- `_spawn_gateway_restart_watcher` now applies that rewrite on Windows and
  threads cwd= / env= into the inlined respawn Popen. Covers both restart
  entry points (`launch_detached_profile_gateway_restart` and
  `launch_detached_gateway_restart_by_cmdline`). CREATE_NO_WINDOW |
  DETACHED_PROCESS | CREATE_BREAKAWAY_FROM_JOB and the breakaway-denied
  fallback are all preserved.

Verified E2E on a real Windows 11 box: drove the actual watcher against a
dummy old-pid; the respawned gateway came up as `pythonw.exe` (zero
console python, no conhost child) and booted fully (housekeeping + kanban
dispatcher started → imports resolved under the base interpreter).

Tests: TestWindowlessGatewayRestartSpec (behavior) +
TestGatewayDetachedWatcherWindowsFlags regression assert. Pre-existing
Linux-only failures on a Windows host (SIGKILL, systemd, docker-root)
confirmed identical on the bare base.
2026-06-26 17:39:46 +00:00
Teknium bb6a4d2a57 docs(nix): mark Nix/NixOS as no longer explicitly supported (#52975)
Add a deprecation banner to the top of the dedicated Nix & NixOS setup
guide and consistency notes at the Nix sections of installation, updating,
and the plugin-distribution guide. Nix is now best-effort only; the
supported install paths are the curl|bash installer, Docker, and Windows.
2026-06-26 10:17:43 -07:00
kyssta-exe c0568ca95f fix(config): use read_raw_config() in migrations to prevent expanding defaults (#40821) 2026-06-26 22:40:52 +05:30
brooklyn! 5cc4009deb Merge pull request #52828 from helix4u/fix/desktop-backend-update-indicator
fix(desktop): show remote backend updates without counts
2026-06-26 11:49:07 -05:00
kshitij 5038678647 Merge pull request #53110 from NousResearch/salvage/42203-find-shell-prefer-usershell
fix(terminal): prefer $SHELL over bash for background process spawning (#42203)
2026-06-26 21:11:57 +05:30
liuhao1024 d9f1f1a1de fix(terminal): prefer $SHELL over bash for background process spawning (#42203)
On macOS, terminal(background=true) silently failed: the process returned a
session_id and exit_code=0 but the command never ran (empty stdout, no side
effects). Root cause is two interacting issues:

1. _find_shell was aliased to _find_bash, which prefers `shutil.which("bash")`
   → /bin/bash (GNU bash 3.2, still shipped on macOS) over $SHELL (/bin/zsh).
2. process_registry.spawn_local runs [shell, "-lic", "set +m; <cmd>"] with
   stdin=/dev/null. bash 3.2 as a login shell sources ~/.bash_profile, which on
   many macOS setups contains `exec /bin/zsh -l`; that exec replaces bash but
   drops the -c argument, so the command is swallowed (exit 0, no output).

Decouple _find_shell from _find_bash: _find_shell now prefers the user's
configured $SHELL on POSIX (the shell they actually log in with), falling back
to _find_bash when $SHELL is unset/missing. _find_bash is unchanged, so callers
that genuinely need bash (e.g. the _run_bash login-shell snapshot) keep bash
semantics. zsh handles -lic correctly even with redirected stdin.

Salvaged from #42219 by @liuhao1024 (authorship preserved via cherry-pick).
On top of the original (8 unit tests covering $SHELL-set/unset/missing/empty,
Windows-ignores-$SHELL, _find_bash-unchanged), added an E2E regression test
that reproduces the real bash-3.2 login-shell swallow (exit 0 / no file) and
asserts the shell _find_shell selects actually executes a -lic background
command. Mutation-verified: reverting _find_shell to the bash alias fails the
$SHELL-preference test. Bug reproduced directly: /bin/bash 3.2 -lic with a
.bash_profile->exec-zsh creates no file; zsh -lic does.

Closes #42203. Supersedes #42290.
2026-06-26 20:45:32 +05:30
xxxigm 65be0061e0 fix(hermes): heal broken managed Node tree instead of PATH fallback
When a Hermes-managed node/npm/npx shim exists but fails --version, redownload
the pinned nodejs.org bundle under HERMES_HOME/node and retry. Do not fall
back to system npm on PATH when a managed tree is present.

POSIX heal probes node, npm, and npx (npm can break while node still runs).
2026-06-26 20:10:20 +05:30
xxxigm 3c5bcd3eee test(hermes): cover broken managed npm fallback in node resolution
Add POSIX runnable-probe coverage plus Windows fallback wiring that skips
a managed npm.cmd when node_tool_runnable rejects it.
2026-06-26 20:10:20 +05:30
xxxigm 9274f73e48 fix(hermes): fall back when managed node/npm fails health probe
A stale or partial Hermes-managed Node tree under the active HERMES_HOME
can leave bin/npm behind while lib/cli.js is missing. File-existence checks
alone made hermes update pick that broken npm and skip healthy system npm
on PATH. Probe managed candidates with --version before preferring them.
2026-06-26 20:10:20 +05:30
kshitij 7b2c51152a Merge pull request #52990 from NousResearch/salvage/52889-backup-projects-kanban
fix(backup): include projects.db and kanban boards in pre-update snapshot (#52889)
2026-06-26 20:09:15 +05:30
0xDevNinja 9ef49cd78f fix(backup): include projects.db, kanban boards, and sibling stores in pre-update snapshot (#52889)
projects.db (per-profile project store) and kanban.db were missing from
_QUICK_STATE_FILES, so the pre-update quick snapshot never backed them up.
On a desktop upgrade, when the update flow removes/replaces the file and the
post-update schema-init re-creates an empty one, all user-created projects,
folder mappings, the active-project pointer, kanban board bindings, and tasks
vanish silently — no error.

Add the per-profile user-created stores to the snapshot set:
- projects.db               — project store
- response_store.db         — gateway conversation history / tool payloads (WAL)
- memory_store.db           — holographic memory facts/entities (WAL)
- verification_evidence.db  — agent verification audit trail
- kanban.db                 — default board (back-compat <root>/kanban.db)
- kanban/boards             — non-default boards (<root>/kanban/boards/<slug>/kanban.db
                              + metadata); workspaces/ and attachments/ subtrees
                              are skipped as large + regenerable.

Also: the directory-branch of create_quick_snapshot now routes *.db through the
WAL-safe _safe_copy_db (SQLite backup() API), matching the top-level file path —
previously a non-default board DB with an open WAL could be copied inconsistently.

Salvaged from #52930 by @0xDevNinja (authorship preserved via cherry-pick).
On top of the original (which covered only projects.db + the default kanban.db),
this adds: non-default-board coverage, the three sibling per-profile DBs that
meet the same upgrade-wipe criteria, WAL-safe directory copies, and a
workspaces/attachments skip to avoid snapshot bloat (×20 retained). 8 tests,
all mutation-verified; E2E verified snapshot→wipe→restore preserves all six
store types on the real code path.

Closes #52889. Supersedes #52930.
2026-06-26 19:23:33 +05:30
Ben 8ab7246c45 fix(gateway): stamp drain marker with instantiation epoch so a durable-volume restart clears it (NS-570)
The external-drain marker .drain_request.json is written under HERMES_HOME,
which on Hermes Cloud is a persistent Fly volume (/opt/data). A begin-drain
marker therefore SURVIVES the post-update machine restart. But the disruptive
lifecycle actions a drain protects (auto-update / image migrate / env edit /
profile change) all restart the machine — which is exactly the signal the drain
is over. The freshly-restarted gateway re-read the orphaned marker on its
startup reconcile and parked itself back in 'draining', refusing every new turn
indefinitely (NS-570: ~52 min until manually cleared).

Fix: stamp the marker with an identity of THIS container/VM instantiation
(kernel boot_id + PID 1 start time, read from /proc) and treat a marker whose
epoch differs from the current instantiation as absent. A deliberate restart →
new PID 1 → new epoch → stale marker ignored → gateway boots 'running'. A marker
written during the current instantiation (the live drain) still matches; an s6
respawn of just the gateway (PID 1/init unchanged) keeps the same epoch, so an
in-flight drain is still honoured (D4a reversibility preserved).

The staleness check is lenient and never fail-closed: a legacy marker with no
epoch, a corrupt/contentless marker, or an environment with no /proc (epoch
unavailable) all degrade to the original presence-only behaviour. NAS is
untouched — it only ever POSTs begin/cancel-drain over HTTP; the marker file is
purely gateway-internal IPC.

The fix is entirely within gateway/drain_control.py; the watcher and the
dashboard endpoint go through the same drain_requested()/write_drain_request()
chokepoints and need no functional change.
2026-06-26 18:59:41 +05:30
Dr1985 e3db1ef92d fix(macos): clearly distinguish launchd supervision from detached fallback in gateway status
## Description

On macOS 26.x, `launchctl bootstrap` and `launchctl kickstart` return exit code 5 ("Input/output error"), which Hermes already anticipates and handles by spawning a detached fallback process. However, the gateway status reporting is ambiguous:

- `gateway status` says "Gateway service is loaded" (because `launchctl list` returns exit 0)
- But `launchctl print` shows `state = not running` — launchd isn't actually supervising anything
- The detached fallback PID running is invisible to the status command
- Users can't tell whether auto-start at login and auto-restart on crash are available

### Root Cause

Two problems in `hermes_cli/gateway.py`:

1. **`_probe_launchd_service_running()`** (line 1067): Determined launchd service liveness solely by `launchctl list <label>` exit code. On macOS 26, this returns 0 even when the service is only *registered* but not running (output lacks a `"PID"` field). This caused `GatewayRuntimeSnapshot.service_running = True` incorrectly, which suppressed the process/service mismatch warning.

2. **`launchd_status()`** (line 3569): Used the same binary "loaded/not loaded" check without inspecting whether launchd actually has a PID, whether a detached fallback is running, or whether auto-start/restart are available.

### Changes

**`hermes_cli/gateway.py`:**

1. **New `_parse_launchd_pid_from_list_output()` helper** — Extracts the PID from `launchctl list` output. When launchd is actively supervising, the output includes `"PID" = <number>;`. When only registered but not running, no PID field is present.

2. **Fixed `_probe_launchd_service_running()`** — Now requires a PID in the `launchctl list` output to confirm launchd is actually supervising. This correctly sets `service_running = False` when launchd has the service registered but `state = not running`, which triggers the existing process/service mismatch detection.

3. **Reworked `launchd_status()`** — Reports clearly separated information:
   - LaunchAgent plist currentness (stale or current)
   - Whether launchd is actively supervising (with PID)
   - Whether a detached fallback PID is running
   - Whether auto-start at login and auto-restart on crash are available
   - When launchd supervision is known to be unavailable, explains why

4. **Persistent unsupported marker** (`~/.hermes/.gateway-launchd-unsupported`) — Written when `_launchd_fallback_to_detached()` is called (launchd exit 5/125). Allows `launchd_status()` to explain *why* launchd can't supervise even when no fallback process is currently running. Cleared automatically when a future bootstrap/kickstart succeeds (e.g., after an OS update fixes the issue).

5. **Updated `_print_gateway_process_mismatch()`** — Distinguishes the managed detached fallback from a genuinely manual `nohup hermes gateway run`, providing accurate guidance for each case.

### Status Output Examples

**Before** (macOS 26, fallback active):
```
Launchd plist: ~/Library/LaunchAgents/ai.hermes.gateway.plist
✓ Service definition matches the current Hermes install
✓ Gateway service is loaded
{
    "Label" = "ai.hermes.gateway";
    "OnDemand" = true;
    ...
};
```

**After** (macOS 26, fallback active):
```
Launchd plist: ~/Library/LaunchAgents/ai.hermes.gateway.plist
✓ Service definition matches the current Hermes install
⚠ Gateway service is registered but launchd is not supervising it
  launchd cannot manage the gateway on this macOS version.
✓ Detached fallback process is running (PID 12345)
  Cron jobs will fire. Stop with: hermes gateway stop
  ⚠ Auto-start at login and auto-restart on crash are NOT available.
```

**After** (normal launchd supervision):
```
Launchd plist: ~/Library/LaunchAgents/ai.hermes.gateway.plist
✓ Service definition matches the current Hermes install
✓ Gateway is supervised by launchd (PID 12345)
  Auto-start at login and auto-restart on crash are available.
```

### Tests

Updated 5 existing tests and added 11 new tests in `tests/hermes_cli/test_gateway_service.py`:
- PID parsing from `launchctl list` output (with PID, without PID, empty, unquoted PID)
- `_probe_launchd_service_running()` requires PID presence
- Unsupport marker lifecycle (write, clear, persist across fallback)
- Marker cleared on successful bootstrap
- `launchd_status()` reporting: supervised, fallback-running, fallback-unavailable
- Existing fallback tests now verify marker creation

### Related Issues

- Issue #23387 (original macOS 26 launchd workaround)
- Issue #42524 (this issue)
2026-06-26 16:30:30 +05:30
kshitij 1c832762a8 Merge pull request #52983 from kshitijk4poor/chore/author-map-dr1985
chore: add Dr1985 to AUTHOR_MAP for launchd salvage
2026-06-26 16:29:22 +05:30
kyssta-exe 07cc567dfa fix(security): add circuit breaker for tirith crashes to prevent agent hangs (#41400) 2026-06-26 15:26:08 +05:30
brooklyn! ca82d0accc Merge pull request #52993 from NousResearch/bb/desktop-clarify-redesign
feat(desktop): redesign the clarify prompt + fix its awaiting-input states
2026-06-26 03:57:43 -05:00
Brooklyn Nicholson 54b50037e1 fix(desktop): treat a pending prompt as paused-on-you, not working
A clarify/approval/sudo/secret prompt blocks the turn on the user, but the UI
treated it as an in-flight turn: the "thinking" timer kept ticking and Esc
interrupted the run — discarding a question you might want to come back to. Add
$activeSessionAwaitingInput (the pet's awaitingInput concept, scoped to the
active session) and use it to suppress the stall indicator and disarm Esc while a
prompt waits. Clear the session's prompts (and needsInput) on Stop and on turn
end so a resolved/aborted turn can't leave a dead panel or a stuck "needs input"
dot.
2026-06-26 03:55:34 -05:00
Brooklyn Nicholson 8559246bfb feat(desktop): rebuild the clarify prompt to match the chat UI
The inline clarify panel used its own card tokens, an animated ring, and
oversized spacing — out of step with every other tool row. Rebuild it on the
shared --ui-*/--conversation-* tokens: a compact panel, letter-key badges
(A/B/C…) that double as a/b/c… shortcuts, an inline content-sizing "Other" field
(CSS field-sizing — no view swap, no layout shift on focus), and a Continue
button so picking an option selects rather than auto-sends. Selection lives on
the letter badge alone (solid primary; outlined while Other is focused-but-empty).
Also settle the panel into the standard tool block once the turn stops running,
so a stopped turn no longer strands a live, unanswerable prompt.
2026-06-26 03:55:29 -05:00
kshitij 1aa458a1e6 Merge pull request #52920 from NousResearch/salvage/38798-toolset-validation
fix(config): surface invalid platform_toolsets instead of silently dropping tools (#38798)
2026-06-26 14:14:55 +05:30
Brooklyn Nicholson da0ed979fa feat(desktop): zoomable primitive — open full, pan/zoom, copy
Add a content-agnostic Zoomable primitive (useZoomPan hook + overlay viewer):
click to open full-screen, wheel-zoom toward the cursor, drag to pan, toolbar
zoom/reset, and an optional copy action. Wire Mermaid diagrams into it with
copy-as-PNG; reusable for other inline content later.
2026-06-26 03:40:49 -05:00
kshitijk4poor 05ba5f3962 chore: add Dr1985 to AUTHOR_MAP for launchd salvage (#42567) 2026-06-26 14:09:11 +05:30
lEWFkRAD 41ede84b93 fix(config): surface invalid platform_toolsets instead of silently dropping tools (#38798)
A config migration (or hand-edit) that leaves an invalid toolset name in
`platform_toolsets` — e.g. the #38798 corruption that rewrote `hermes-cli` to
the non-existent `hermes` — silently disabled all affected tools:
resolve_toolset() returns [] for an unknown name, so the agent quietly lost its
tools with no error, warning, or log entry and degraded to text-only replies.

Surface it loudly at two points:
- After migration (migrate_config): validate platform_toolsets and record/print
  a warning per unknown name, with a `hermes-<platform>` suggestion when that
  would have been valid (the exact #38798 shape).
- At runtime (_get_platform_tools): if a platform was explicitly configured but
  every toolset name is invalid, log a warning when tools are resolved for a
  session — so an ALREADY-corrupted config is caught at startup, not only on the
  next `hermes update`.

Logic lives in a new pure, side-effect-free helper (toolset_validation.py) with
validate_toolset injected, so it is unit-testable without the tool registry.

Note: the original v25→v26 migration that caused the corruption no longer
exists (config format is now v30; no migration step rewrites toolset names).
This change is the durable defense against the silent-failure mode regardless
of cause, matching the issue's "Expected: log a warning".

Salvaged from #39207 by @lEWFkRAD (authorship preserved via cherry-pick).
Tests: 9 helper cases (incl. the #38798 corruption shape, mixed valid/invalid,
zero-tools state, non-dict/scalar/non-string) + a runtime caplog test — both the
helper warning and the runtime guard mutation-verified to fail without the fix.

Closes #38798. Supersedes #39581 (prevent-in-v25→v26 — that path is gone),
#41006 / #40208 (repair-migration for already-corrupted configs).
2026-06-26 14:07:43 +05:30
Brooklyn Nicholson e36d9862ec feat(desktop): render embeds, fences and alerts in assistant markdown
Wire the embeds module into the markdown surface: bare provider autolinks unfurl
to inline embeds, ```mermaid/```svg fences route to the rich renderers, and
`> [!NOTE]`-style blockquotes become alert callouts. Labeled links stay plain.
2026-06-26 03:22:14 -05:00
Brooklyn Nicholson 0c190083cd feat(desktop): lazy embed renderers + fenced diagrams/alerts
Per-kind renderers, each a lazy split chunk: plain-iframe video/maps (wheel
chains to the transcript; maps gate scroll behind ⌘), the in-document
blockquote-script path for X/Instagram, the dark Spotify player, and the
YouTube iframe. Adds Mermaid and DOMPurify-sanitised SVG fences and GFM alert
callouts, all sized to 33dvh and theme-matched to avoid white color-scheme
artifacts. Main-process stamps a Referer on YouTube embed requests.
2026-06-26 03:22:08 -05:00
Brooklyn Nicholson 81ac562bf0 feat(desktop): inline embed detection + module primitives
Pure, synchronous URL→descriptor matchers for YouTube, Vimeo, Instagram,
Pinterest, TikTok, X, Spotify, Google Maps and OpenStreetMap, plus the shared
embed primitives (error boundary, fail card, escape-html, dark-mode hook,
sizing token). Declares the mermaid + dompurify deps used by the fenced
renderers.
2026-06-26 03:21:59 -05:00
helix4u 063fe4f6ef fix(auxiliary): fallback on invalid provider responses 2026-06-26 13:49:46 +05:30
Teknium 6ff7e78649 feat(egress): smoother UX — restart command, auto-restart on setup, .env key discovery
- Add `hermes egress restart` (stop-then-start) so applying a config /
  token / Bitwarden-rotation change is one command instead of the
  stop+start dance.
- `hermes egress setup` now offers to restart a running daemon after
  rewriting config/tokens (asks on a tty; `--restart` / `--no-restart`
  for non-interactive control), so changes take effect without the
  operator remembering a manual restart.
- `setup` discovers provider keys kept only in ~/.hermes/.env, not just
  exported shell vars — no more confusing 'no provider keys found' when
  the keys plainly exist.
- Tests + docs updated.
2026-06-26 01:11:38 -07:00
Teknium eb7aae5816 chore(egress): drop committed infographic PNG from tree
Infographics live at their hosted URL and are referenced from the PR
body — they are never committed to the repo (repo-cleanliness rule).
Removes the 1.8MB infographic/iron-proxy-egress/infographic.png that
the original PR added to the tree.
2026-06-26 01:11:38 -07:00
teknium1 ae1bfa1806 fix(egress): close GOOGLE_API_KEY coverage gap + config/mappings write TOCTOU
Two correctness gaps surfaced in the review thread (texasich) that
survived the prior rounds:

- GOOGLE_API_KEY was warn-only while GEMINI_API_KEY was fail-closed,
  despite both authenticating the same generativelanguage LLM endpoint
  (auth.py treats them as interchangeable). An operator with only
  GOOGLE_API_KEY set + fail_on_uncovered_providers got false coverage.
  Added it to _LLM_SPECIFIC_NON_BEARER_PROVIDERS.
- write_proxy_config / write_mappings chmod'd AFTER os.replace, leaving
  the token-bearing files briefly world-readable under a slack umask
  (the 0o700 state dir mitigates but same-uid race remained). chmod the
  temp file BEFORE the atomic replace, matching the CA-key write path.

Tests: assert GOOGLE_API_KEY in blocked tier; assert proxy.yaml +
mappings.json land at 0o600.
2026-06-26 01:11:38 -07:00
Teknium ad978ed962 feat(egress): iron-proxy credential-injection firewall for sandboxes
Rebuilds the iron-proxy egress feature cleanly onto current main. The
original feat/iron-proxy branch had diverged from main with an
unmergeable history (no usable merge-base after main history motion),
so the feature's content diff was re-applied onto a fresh main cut and
the three config/docs conflicts (commands.py status/egress, config.py
proxy vs computer_use, slash-commands.md) resolved keeping main's
content plus the egress additions.

Optional, off-by-default TLS-intercepting egress proxy for remote
terminal sandboxes. Sandboxes hold opaque proxy tokens; iron-proxy
swaps them for real provider API keys at the network boundary.

Includes the full review-cycle hardening:
- P0/P1/P2 rounds (GodsBoy, stephenschoettler, arshkumarsingh,
  annguyenNous, maxpetrusenko, sxuff findings)
- v0.39 schema realignment + Docker bridge-bind/listener-role fixes
- Docker UX/enforcement hardening

Salvaged security fixes folded in with credit:
- Three P0 gaps (version-probe env scrub, Bitwarden ImportError
  fail-closed, container-reuse egress-boundary) + Docker v29.5.3
  empty-label edge — kuangmi-bit (#48073)
- P1/P2 (fail-closed replace.require:true, NODE_OPTIONS CA-flag
  conflict, GPG checksum verify, threat-model wording) — Bartok9 (#48076)

Co-authored-by: kuangmi-bit <kuangmi@deeparchi.com>
Co-authored-by: Bartok9 <danielrpike9@gmail.com>
2026-06-26 01:11:38 -07:00
teknium1 fbfccbb3ee fix(security): align cron invisible-unicode set with install-time scanner
The cron runtime tripwire (_scan_cron_prompt) used a 10-char invisible-unicode
set while the install-time scanner (threat_patterns.INVISIBLE_CHARS) flags 17.
The cron-local set was missing U+2062-U+2064 (invisible math operators) and
U+2066-U+2069 (directional isolates), so a directive obfuscated with one of
those codepoints (e.g. "ig<U+2063>nore all previous instructions") slipped past
the runtime cron gate while being caught at install time.

Import the canonical set so the cron tripwire and install scanner can't drift
apart again. Emoji-ZWJ protection (_zwj_has_emoji_neighbour) is unchanged.

Fixes #35075

Co-authored-by: rlaope <piyrw9754@gmail.com>
2026-06-26 01:11:11 -07:00
Shannon Sands a0dc92450b Split dashboard PTY reconnect tests 2026-06-26 01:06:02 -07:00