Commit Graph

28271 Commits

Author SHA1 Message Date
Teknium a094d45095 ci: retrigger — workflows never dispatched for 4d87290d39 2026-08-19 16:32:20 -07:00
Teknium 803397ecc3 feat: wall-clock run budget — wrap-up injection at 80% and deadline-scaled stale timeouts (agent.run_budget_seconds / --run-budget) 2026-08-19 16:32:17 -07:00
Brooklyn Nicholson 7f3d255931 test(desktop): cover the terminal overlay hiding on an unfocused tab switch 2026-08-19 18:31:57 -05:00
Brooklyn Nicholson 2473e56859 fix(desktop): stand the terminal overlay down when its tab loses focus
The persistent terminal is a position:fixed overlay that chases its slot's
rect, and the whole tracker — visibility included — was gated behind the
renderer pause. Switching tabs while the window is unfocused therefore left
the overlay parked over the zone at full opacity with pointerEvents:auto, so
the chat underneath was unreachable until something refocused the window.

Visibility is correctness rather than perf, so sample it on every wake even
while paused; the rect chase, which is the part that forces layout, stays
gated.
2026-08-19 18:31:57 -05:00
Teknium 4a83b03c3a fix: read tool_budget config via load_config_readonly (config read guard) 2026-08-19 16:31:16 -07:00
Teknium 09e657793e feat: MCP tool results spill at 50K and carry upstream-elision warnings
Composio-style MCP servers return un-paginated 22-47K-char payloads that
sail under the generic 100K per-result spillover threshold, bloating
context and ballooning per-turn reasoning time on long conversations.
Competitors cap harder (OpenCode/pi 50KB, Claude Code 30K, Codex ~10K
tokens). Three changes:

- mcp_* tools spill at a tighter 50K default (BudgetConfig.mcp_result_size,
  config-overridable via tool_budget.mcp_result_size_chars; pinned and
  per-tool overrides still win; capped by the context-scaled default).
- The persisted-output preview now teaches recovery: page the saved file
  with read_file or process with execute_code instead of re-requesting the
  same data from the remote API.
- Untrusted/MCP string results are scanned (bounded, first 64KB) for
  provider-side elision markers ('...N more items', "has_more": true,
  'saved to sandbox', data_preview) and get ONE cache-safe incompleteness
  notice appended at result-construction time, before untrusted wrapping —
  so the model stops treating provider-elided enumerations as complete.
- Hard 2M-char allocation cap in mcp_tool.py (text, error, and
  structuredContent paths) so a pathological multi-MB server payload is
  bounded before it propagates, while ordinary large results reach
  spillover intact. Distilled from #56060/#56072/#56511 (issue #56059);
  supersedes their 50K lossy truncation with spillover-friendly semantics.

Docs: configuration.md spillover-budget section + cli-config.yaml.example.

Co-authored-by: Stoltemberg <215755014+Stoltemberg@users.noreply.github.com>
Co-authored-by: AlexFucuson9 <295703459+AlexFucuson9@users.noreply.github.com>
Co-authored-by: Tranquil-Flow <66773372+Tranquil-Flow@users.noreply.github.com>
2026-08-19 16:31:16 -07:00
Teknium 4d87290d39 feat: keyless web traffic splits 50/50 between Exa and Parallel like opencode
Unpinned zero-credential installs now pick Exa or Parallel by the
parity of the per-process random session id (stable within a process,
even split fleet-wide) instead of always favoring Parallel. An explicit
hermes tools selection (web.backend / per-capability keys) bypasses the
split entirely; the runner-up vendor stays in the walk as fallback.

Live E2E: 6 fresh processes split 3/3 between vendors, each performed
a real keyless search via its picked endpoint; explicit pin verified.
2026-08-19 16:22:30 -07:00
Teknium d762ed9b3c feat: execution-discipline guidance now reaches all tool-capable models (config model.execution_guidance)
Un-fences OPENAI_MODEL_EXECUTION_GUIDANCE from the gpt/codex/grok substring
check and gives it its own injection gate, independent of
tool_use_enforcement, controlled by config.yaml `agent.execution_guidance`
(auto/true/false/list — same semantics as tool_use_enforcement). The "auto"
list (EXECUTION_GUIDANCE_MODELS) now also covers deepseek, kimi, qwen, glm,
minimax, mimo, and mistral.

Composio agentic-eval traces showed Hermes+DeepSeek/Kimi failing where
competitors passed: financial math done in prose, no read-back after
external writes, malformed identifiers "repaired", completeness claimed
despite count mismatches. The discipline block existed but those models
never received it.

The block is extended with compact clauses distilled from that analysis:
- external-write read-back (tool-call success is not task success; internal
  file edits already confirmed by the tool are not re-verified)
- count reconciliation (declared totals/has_more are hard assertions)
- literal preservation (never normalize identifiers that fail a stated
  format; lookup success does not validate a malformed token)
- retry-differently (empty/partial/suspiciously narrow results get a
  broader retry before concluding)
- completion gated on verification (done = every named acceptance
  criterion verified, never a plausible subset)

The todo tool description now encourages enumeration-as-checklist for
"all N items" tasks and gates completed status on verified work, never
intent.

Guidance is chosen once at session start keyed on model name, so the
system prompt stays byte-stable for the life of a conversation.

Supersedes/absorbs prior contributor proposals: #20588, #35087, #41874
(MiMo), #53847 (GLM tool-calls-as-text stall).

Co-authored-by: Mat-London <56627804+Mat-London@users.noreply.github.com>
Co-authored-by: intelac <8803887+intelac@users.noreply.github.com>
Co-authored-by: 6ylqq <51219463+6ylqq@users.noreply.github.com>
Co-authored-by: tauros1983 <267660491+tauros1983@users.noreply.github.com>
2026-08-19 16:16:37 -07:00
Teknium 271e49a8ff fix(bot-mode): accept both host.connections() shapes in the Create-on picker normalize
The SDK now returns the registry rows per its documented contract
(salvaged #89893), while desktops predating the SDK unwrap resolve the
raw registry envelope. The plugin normalize accepts both, so the picker
works across the transition; regression test updated to pin the
dual-shape normalize.
2026-08-19 16:10:10 -07:00
hukla a46fe01251 chore(sdk): remove unrelated session helper from #89893 2026-08-19 16:10:10 -07:00
hukla b40d2019e8 fix(sdk): preserve primary in registered connections 2026-08-19 16:10:10 -07:00
hukla aa2cec721f test(sdk): cover registry primary connection mapping 2026-08-19 16:10:10 -07:00
hukla c825be4c77 test(sdk): cover connection registry list contract 2026-08-19 16:10:10 -07:00
hukla 6ad587236f fix(sdk): return registered connection list 2026-08-19 16:10:10 -07:00
Teknium 19113b34d1 test(tools): repin selector/picker tests to the provider-string contract
Update the sibling tests that pinned the old use_gateway-writing
contract: image/video selector and reconfigure rows now assert the
single provider string ('nous' managed / 'fal' BYOK) plus legacy-key
popping, the stt/video picker writes drop the use_gateway expectation,
the web_server managed-browser select asserts the persisted 'nous'
cloud_provider, and explicit-local STT pins no-cloud-fallback against
a stored raw-config selection.
2026-08-19 16:10:01 -07:00
Teknium aa3c5e59d3 fix(tools): honor raw stt.provider: local; finish _reconfigure_provider provider-string migration
Two real gaps the CI-red sibling tests exposed:

- read_selection() treated EVERY raw stt.provider: local as the legacy
  DEFAULT_CONFIG seed and reported no-selection — but the seed never
  reached config.yaml (save_config strips schema defaults), so a
  picker- or hand-written local pick was silently discarded and the
  autodetect ladder could route an explicit local user to cloud STT.
  A raw 'local' is now a genuine selection; the merged-view ambiguity
  note replaces the over-broad shim (mirror comment updated in
  nous_subscription._selected_provider and _get_provider).

- _reconfigure_provider was half-migrated: the tts/stt/browser/web
  branches and the managed-category fallthrough still wrote
  use_gateway flags and vendor names for managed rows. They now write
  the single provider string ('nous' for managed rows) and pop the
  legacy key, matching _write_provider_config.
2026-08-19 16:10:01 -07:00
Teknium 89e75f4770 test(tools): pin strict provider-string selection per category
New tests/tools/test_strict_provider_selection.py covers read_selection
semantics (legacy use_gateway interpretation, seeded stt local, empty
strings, browser.backend vs cloud_provider) and the three strict
behaviors per category: managed 'nous' selection wins over present
direct keys, a vendor selection with missing credentials raises the
selection-naming error with NO managed call, and never-configured
installs keep today's autodetect. Updated the tests that pinned the old
credential-first precedence (TTS resolver gateway override, STT silent
managed fallback, web invalid-backend reroute, video_gen picker writes).
Sabotage-verified: reverting the image FAL strict switch makes the new
managed-selection tests fail.
2026-08-19 16:10:01 -07:00
Teknium b10c5a8084 fix(cli): persist one provider string per picker row; mirror strict routing in status
Every hermes tools row now writes exactly one selection value per
category — managed 'Nous Subscription' rows write 'nous', BYOK rows the
vendor name (including the historically-unset BYOK-FAL image row) — and
use_gateway is no longer written; fresh picks drop any legacy key so the
read-time shim cannot override them. The non-managed clear now resolves
the category from the row's own markers, covering plugin-injected rows
the TOOL_CATEGORIES loop missed. Setup-flow writers (managed defaults,
gateway enablement) store 'nous', and the feature-state mirrors in
nous_subscription.py compute per-category selections with the same
legacy interpretation so hermes status matches runtime: a stored vendor
selection pins direct (managed availability no longer lights it up) and
an explicit non-camofox selection beats a stray CAMOFOX_URL.
2026-08-19 16:10:01 -07:00
Teknium 7f83d3808c fix(browser): strict cloud-provider selection; camofox becomes a selection
An explicitly stored browser.cloud_provider that names no registered
plugin now raises the honest selection-naming error instead of warning
and silently auto-detecting; the auto-detect walk (including the managed
gateway entitlement probe) runs only when no cloud_provider key was ever
written. The 'nous' selection routes to the Browser Use provider, whose
config resolver is now a strict switch: 'nous' => managed only, stored
vendor => direct BROWSER_USE_API_KEY only with a selection-naming error
when missing. Camofox is selected via browser.cloud_provider: camofox;
CAMOFOX_URL stays the server ADDRESS only and can no longer override an
explicit different selection (never-configured installs keep the legacy
env-var activation).
2026-08-19 16:10:01 -07:00
Teknium 099258ef48 fix(voice): route TTS/STT OpenAI audio on the stored selection, not credentials
Both _resolve_openai_audio_client_config resolvers now switch on the
stored provider string: 'nous' (or legacy use_gateway: true) => managed
openai-audio gateway only, erroring by selection name when unentitled —
the STT twin previously never read the stored gateway intent at all, so
a direct OPENAI_API_KEY silently overrode the Nous Subscription pick;
stored vendor => direct credentials only with a selection-naming error
on missing keys (no silent managed fallback); never-configured keeps the
legacy ladder. DEFAULT_CONFIG stops seeding stt.provider: local, and the
seeded value on existing configs is treated as no-selection so autodetect
keeps working for that installed base.
2026-08-19 16:10:01 -07:00
Teknium d7119ea2a6 fix(web): honor the stored web backend selection; no silent backend swaps
_get_backend returns the stored web.backend verbatim (mapping the managed
'nous' selection to the firecrawl provider) — unknown names surface the
honest selection-naming error at dispatch instead of silently rerouting
through the credential ladder, which now runs only on never-configured
installs. _get_capability_backend no longer discards an explicit
search/extract backend when its availability probe fails. The firecrawl
client resolves strictly: 'nous' => managed gateway only (unavailable =>
selection-naming error), stored vendor => direct only (no FIRECRAWL key
=> error, never a silent managed fallback billed to Nous).
2026-08-19 16:10:01 -07:00
Teknium 2dea073a1c fix(tools): dispatch image/video FAL strictly on the stored hermes tools selection
Add read_selection()/selection_exists()/selection_error() to
tool_backend_helpers: one provider string per category ('nous' = managed
Nous Tool Gateway, vendor name = direct with the user's own credentials,
no key ever written = legacy credential autodetect). Legacy configs are
interpreted at read time only (use_gateway: true => nous); nothing is
migrated on disk, and the DEFAULT_CONFIG-seeded stt.provider: local is
treated as never-configured.

_resolve_managed_fal_gateway / _resolve_managed_fal_video_gateway now
switch on that string: 'nous' routes managed only (unentitled => error
naming the selection), a stored vendor routes direct only (missing
FAL_KEY => error naming FAL_KEY and the selection, no silent managed
reroute), and FAL_KEY presence no longer selects the route. Krea's
model-driven managed interception now requires no stored provider (or
the managed selection) instead of merely provider != krea, and the
image/video registries map the 'nous' selection to the FAL plugin.
2026-08-19 16:10:01 -07:00
Teknium 9ec5750aca fix: widen reasoning-effort wire translation to sibling sites (#89503 class)
The chat_completions chokepoint fix (ultra->max for every model,
cherry-picked from #89509) has siblings with the same bug shape:

- codex.py: ultra->max was gated on gpt-5.6 only; now baseline for all
  Responses-API models (backend-specific branches still override).
- Kimi top-level reasoning_effort: K3 accepts low/high/max only —
  'medium' and upper-ladder levels were dropped to the medium default
  (400s on K3, ladder inversion on K2). Full ladder mapped per family,
  mirroring the kimi-coding plugin's K3 map.
- TokenHub: 'minimal' fell through to the 'high' default (asked least,
  got most); full ladder now mapped onto low/medium/high.
- auxiliary_client Responses path: ultra->max alongside the existing
  minimal->low clamp.
- custom provider plugin: ultra capped at max instead of forwarded
  verbatim to GLM/vLLM/SGLang backends that reject it.
- copilot plugin: ad-hoc downgrade rules replaced with the shared
  clamp_reasoning_effort_to_supported ladder walk so ultra/max resolve
  to the strongest supported level instead of medium (#74295).

Sabotage-verified: new sibling-site tests fail 6/10 without the fixes.
2026-08-19 16:04:22 -07:00
liuhao1024 a664429192 fix(agent): cap the ultra reasoning level at the wire vocabulary
Hermes' internal effort vocabulary extends the wire set with ultra
(documented by /reasoning as none..xhigh|max|ultra). OpenAI-compatible
wires — OpenRouter chief among them — accept exactly
max|xhigh|high|medium|low|minimal|none and reject the extension with
HTTP 400, so an ultra configured while the default model was Anthropic
worked (the Anthropic adapter maps its own levels) but leaked
untranslated the moment a per-job override pinned a non-Anthropic
model, failing every call for that job.

The wire-compat chokepoint for this transport previously mapped
ultra to max only for gpt-5.6; generalize the cap to every model.
2026-08-19 16:04:22 -07:00
Teknium 010c9925e3 fix(bot-mode): roster age, pulse, unread, and sort now see canonical Bot Chat activity
The canonical Bot Chat is hidden from session lists by design, so
profiles.list's last_session never advances when you message a bot there.
PR #88690 moved the roster PREVIEW to preferred_session but left every
activity signal on last_session — a bot you just messaged showed '6d ago',
never pulsed, never badged, and sorted below stale bots.

New botActivitySession(bot) helper returns the fresher of preferred_session
(the pinned Bot Chat, resolved precisely by the backend) and last_session
(newest visible conversation). All four activity sites key off it now:

- row age label (relativeTime)
- active-now pulse dot + activeBots strip
- unread watermark + activity toast preview
- roster recency sort (activityOf)

Older gateways without the preferred_session resolver degrade to
last_session exactly as before. Backend untouched.

Tests: extracted the real helper into the vm harnesses (no stub drift),
5 new behavior tests; sabotage-verified they fail against the old code.
2026-08-19 16:04:13 -07:00
Teknium 08b7fad3a5 test: registry zero-credential resolution may return a keyless-capable provider
test_no_config_no_credentials_returns_none pinned 'resolved provider
must be is_available()' — stale now that the keyless tier resolves
Parallel/Exa with is_available()=False + is_keyless_available()=True.
Accept keyed OR keyless-capable results (env-leak detection intact).
2026-08-19 15:38:08 -07:00
Teknium f08d3e400f feat: hermes tools lets Exa/Parallel users pick the free keyless or paid keyed endpoint
Exa and Parallel now each render as two picker rows in hermes tools —
'Free (keyless)' and 'Paid (API key)'. Selection persists to
web.provider_tier.<name>:
- free: always the anonymous public endpoint, even with a key set
- paid: always the keyed SDK path; missing key errors instead of
  silently downgrading to the free tier (is_keyless_available also
  returns False so the auto-fallback walk can't route there)
- unset: auto (key present -> paid, else keyless)

Mechanism: get_setup_schema() gains a 'variants' list the picker
flattens into sibling rows sharing one web_backend; selection writes
the tier via both _write_provider_config sites; active-row detection
matches the tier (auto mirrors use_keyless). Routing goes through a
single use_keyless() chokepoint shared by search+extract in both
providers.

Live E2E: tier=free with a fake key present searched keyless OK (a
keyed call would have 401'd); tier=paid without key errored naming
PARALLEL_API_KEY; picker rows verified for both vendors x both tiers.
2026-08-19 15:36:20 -07:00
Teknium 2d9dad0bae docs: correct Exa keyless rate-limit characterization
A 12-request sequential burst from the same IP that earlier saw the
free-tier rate-limit error went 12/12 OK — the limit is a transient
burst/load control, not a tight standing per-IP quota. Soften the docs
and setup-schema wording accordingly (opencode users hit Exa keyless
as their default path in practice without throttling).
2026-08-19 15:24:27 -07:00
Teknium 96c2fd3c04 feat: web search/extract now work keyless on fresh installs via Parallel + Exa free tiers
With zero web credentials configured, web_search/web_extract previously
resolved to the nonfunctional firecrawl sentinel and errored. Now the
backend resolution walks a strictly-last keyless tier: Parallel's and
Exa's public anonymous MCP endpoints (the same free tiers opencode ships
as its default search path).

- plugins/web/keyless_mcp.py: minimal JSON-RPC tools/call client for
  mcp.exa.ai + search.parallel.ai (SSE + plain JSON parsing, typed
  errors, per-process random session id, no user identifiers)
- WebSearchProvider.is_keyless_available(): separate weaker tier that
  never leaks into is_available(), so keyed setups are never pre-empted
- Exa/Parallel providers: route to keyless endpoints when their key is
  absent; keyed SDK path unchanged
- registry + _get_backend(): keyless walk (parallel -> exa) strictly
  after every keyed/importable candidate; check_web_api_key() lights
  the tools up on zero-credential installs
- web.keyless_fallback config key (default true) to disable the tier
- docs: web-search.md + configuration.md

E2E-verified against both live endpoints from an isolated HERMES_HOME
(search + extract via the real dispatchers, disable-flag negative path).
2026-08-19 15:15:27 -07:00
Tom 7b25941b0e fix(bot-mode): unwrap connections registry object so 'Create on' picker renders
host.connections() resolves the IPC handler hermes:connections:list, which
returns the registry OBJECT ({version, primary, connections: [...]}) — not a
bare array. CreateAgentDialog did setConnections(Array.isArray(value) ? value
: []), so on a multi-connection desktop the picker gate
(Array.isArray(connections) && connections.length > 1) never fired and the
'Create on' picker stayed hidden, making cross-machine bot creation
impossible despite the multi-connection feature being documented.

Any new agent is still created on the active gateway (unchanged behaviour);
the picker is the only path that regressed. The built-in Connections UI
(refreshConnectionsRegistry) consumes the same registry object, so the IPC
handler contract is left untouched.

Adds a regression test asserting the unwrap and that the IPC contract is
preserved. Plugin suite: 309/309 pass.
2026-08-19 15:06:57 -07:00
Falko 8430c1b4da test(codex): cover nested retention entry paths 2026-08-20 03:15:14 +05:30
kshitijk4poor f6d1d774a1 refactor(codex): name the dropped shape in the wire-guard warning
Review polish from the 3-angle pass on the final stack:

- The warning now says WHICH shape leaked (top-level, extra_body, or both).
  Relay injects top-level while request_overrides typically inject via
  extra_body, so the shape identifies the offending middleware when
  debugging.
- Fold the 'always returns a fresh mapping' assertion into the parametrized
  real-endpoint test (the caller mutates the result with stream=True, so the
  copy contract is load-bearing on no-drop paths too) and drop the
  SimpleNamespace stub test it strictly subsumes. The nested-preserve stub
  stays: the parametrized test only exercises top-level retention.
2026-08-20 03:15:14 +05:30
kshitijk4poor 26530e7df5 fix(codex): strip nested extra_body retention at the consumer Codex wire
The wire guard only removed the top-level prompt_cache_retention kwarg, but
the OpenAI SDK merges extra_body into the outgoing JSON body, so a nested
extra_body.prompt_cache_retention reaches chatgpt.com/backend-api/codex just
the same and still triggers the non-retryable HTTP 400. Both injection
vectors are real and probe-verified: the Relay overlay's 'key not in
baseline' arm admits an interceptor-added extra_body, and
request_overrides={'extra_body': {...}} lands verbatim in build_kwargs
output.

Close the gap in the same helper: strip the nested field too (copy-on-write,
never mutating the caller's mapping), drop extra_body entirely when it
empties, and log the same warning. Compatible endpoints keep nested
retention untouched.

Mutation-verified: removing the extra_body leg fails both new nested tests.

Reported by egilewski's review on #89969.
2026-08-20 03:15:14 +05:30
kshitijk4poor ba4bc39afd test(codex): pin the retention drop to real endpoints
The salvaged compatibility test stubs `_is_codex_backend=lambda: False` on a
SimpleNamespace, so it proves the helper honors its own boolean but not that
the boolean is right for any real endpoint. A predicate change that widened
the drop onto retention-supporting hosts would keep it green.

Adds a parametrized test that builds a real AIAgent per base URL and asserts
the drop only fires for chatgpt.com/backend-api/codex, while api.meta.ai,
bedrock-mantle.*.api.aws, api.openai.com and a same-host/different-path
backend keep their supported 24h value. Also asserts prompt_cache_key
survives untouched on every endpoint, since retention and cache-key routing
are independent and the guard must not disturb caching.

Verified non-vacuous: relaxing the guard's condition to drop on every
endpoint fails 4 of the 6 cases (Meta, Bedrock, OpenAI, non-codex path).

Drive-by on the guard itself: drop the dead `None` default on the `pop` that
is already gated by an `in` check, and record why the predicate is resolved
via getattr -- run_codex_stream is driven with lightweight stand-in agents
that lack `_is_codex_backend`, so a bare call would raise AttributeError.
2026-08-20 03:15:14 +05:30
kshitijk4poor c7b6854ba1 chore: map contributor email for #89969 salvage
f4lko@pm.me -> thacid22. The email is linked to the thacid22 GitHub
account on the PR's commit, so check-attribution can resolve it once
the mapping file exists on the branch.
2026-08-20 03:15:14 +05:30
Falko 8e2949495a fix(codex): strip unsupported cache retention at wire 2026-08-20 03:15:14 +05:30
Teknium ce9d48ce85 chore: map contributor email for Haik-G 2026-08-19 14:44:36 -07:00
Teknium aba96d5251 feat(image-gen): route live-catalog models to the Image API; merge picker catalogs; docs
Follow-ups on top of the salvaged #82631 surface:

- _select_surface: an unknown model id found in the live /images/models
  catalog now ROUTES to the dedicated Image API instead of only logging a
  hint — without this, a model picked from the live picker that postdates
  the curated snapshot would fall onto chat-completions and fail. Curated
  defaults stay pinned to chat (no behaviour change for existing setups);
  offline probes still fall back to chat. _HINTED_MODELS removed.
- list_models (OpenRouter): union of the live GET /images/models catalog
  (43 models today) and the chat-completions image models, deduped,
  defaults first; curated metadata wins for known ids, API names for the
  rest. Nous Portal (no /images route) keeps its chat-only catalog.
  Offline fallback: static chain + curated Image API snapshot.
- Tests updated/added: unknown-id routing (flipped from the hint-only
  pinning test), non-catalog id stays on chat, merged-picker union/dedupe/
  order, Nous exclusion.
- Docs: image-generation.md gains the OpenRouter Image API section and an
  editing-support row.

Live-verified: picker lists 43 models; generation succeeded through the
dedicated API on google/gemini-3.1-flash-lite-image and on the previously
unreachable black-forest-labs/flux.2-klein-4b (config-selected, no kwarg).
2026-08-19 14:44:36 -07:00
AI Staff d6e6e8b602 feat(plugins): add OpenRouter Image API surface to openrouter image_gen backend 2026-08-19 14:44:36 -07:00
Jack Lau 49cc3708e5 fix(dashboard): coalesce repeat gateway restarts for a short window
`_spawn_gateway_restart` already reuses an in-flight `hermes gateway
restart` child so a double-clicked button cannot start two racing
restarts. That guard evaporates exactly when it is needed most: the
child exits as soon as it has handed the restart to the supervisor (or
to the running gateway), long before the gateway is actually back, so a
stale cached dashboard frontend re-firing its own restart every few
seconds cleared the guard on every attempt and started a fresh restart
each time.

#89034 measured the result on an s6-supervised container: 77
`gateway-restart started` entries, 17 of them inside one minute. Each
one SIGHUPs a gateway that is still coming up, and killing it
mid-FTS5-write corrupted `state.db` ("database disk image is
malformed", 203x in agent.log) until the operator recreated the file by
hand.

Requests for the same profile within GATEWAY_RESTART_COOLDOWN_SECONDS of
the last spawn are now coalesced onto that spawn and logged, so a storm
produces one restart instead of one per request. The window is fixed
rather than health-gated on purpose: a gateway that never comes back
would leave a health-gated restart action permanently inert, which is a
worse failure than the flood it prevents. The cooldown state is kept
outside `_ACTION_PROCS` because completed action children are reaped out
of that table, and a guard that disappears when the child exits is the
bug being fixed.

Only the *frontend-flood* half of #89034 is addressed here. The s6
`finish` death-cap the report also asks for is a separate change to
`hermes_cli/service_manager.py` with a much larger blast radius, and is
left for a maintainer decision.
2026-08-20 02:58:31 +05:30
hermes-seaeye[bot] 657550716f fmt(js): npm run fix on merge (#90248)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-19 20:21:18 +00:00
Sara Burke a946169533 Merge latest upstream into fix/remote-bot-routing
Carry the verified connection-bound routing branch through the remaining
upstream changes at fe2752a05 without rewriting prior signed commits.
2026-08-19 16:16:26 -04:00
Sara Burke 336b3216d5 Merge current upstream into fix/remote-bot-routing
Preserve the connection-bound Bot and session routing implementation while
adopting upstream's modular Desktop API split and all changes through
b2057c168.
2026-08-19 16:14:41 -04:00
brooklyn! fe2752a05d Merge pull request #90239 from NousResearch/bb/close-preview
The agent can close the preview pane, not just open it
2026-08-19 15:12:37 -05:00
Brooklyn Nicholson 7f73978ee4 feat(desktop): close preview tabs on preview.close
Omit url to drop the whole rail; a url/path/label match closes that tab.
Background sessions still cannot yank the user's preview shut.
2026-08-19 14:59:52 -05:00
Brooklyn Nicholson 60e9ed12c4 feat(tools): close_preview so the agent can dismiss the pane it opened
open_preview and read_preview could drive the page, but nothing could close
the pane. Same desktop_ui / session-source gate as the rest of the GUI tools.
2026-08-19 14:59:51 -05:00
kshitij f0ffcbc753 fix(approval): align the execute_code CLI fall-through with its sibling guards
Follow-up to the salvaged fix. Three parity gaps in the new CLI branch:

- Timeout arm dropped the denial-breaker addendum that the same function's
  gateway arm and check_all_command_guards' CLI tail both append, so a
  tripped breaker went unreported on a timeout.
- Human deny called _record_denial(), advancing a tally scoped to guardian
  LLM DENY verdicts. Neither sibling CLI tail does this, so three
  deliberate user denials escalated to breaker hard-stop text.
- The platform-marker half of the leak (HERMES_SESSION_PLATFORM set, no
  HERMES_EXEC_ASK) was unpinned; it reaches the same branch.

Adds a platform-marker regression test plus two breaker-parity guards, and
clears the process-global _denial_tally in the shared fixture so a leaked
tally can't bleed the escalated addendum into unrelated assertions.
2026-08-20 01:16:25 +05:30
pierrenode 16af3bed8c fix(approval): route check_execute_code_guard through the CLI fall-through too
e37a0321eb fixed _run_approval_gate and check_all_command_guards: when
HERMES_EXEC_ASK (or a session platform marker) leaks into an interactive CLI
process with no gateway notify callback registered, those two functions now
prefer the registered CLI Dangerous Command panel over a silent
pending_approval nobody can see.

check_execute_code_guard — the whole-script gate for execute_code, a
separate function with its own copy of the same notify_cb-less
short-circuit — never got the same treatment. It doesn't even accept an
approval_callback parameter. In the same leaked-ask-mode-into-CLI scenario,
execute_code calls still silently drop into pending_approval with the panel
never shown, even though a CLI callback is registered.

Compute is_cli/approval_callback the same way the two fixed functions do,
and when _should_fall_through_to_cli_approval() says yes, run the same
hook-fire -> prompt_dangerous_approval -> hook-fire -> choice-branch
sequence _run_approval_gate's tail already uses, adapted to this function's
own message/persistence conventions (smart-denied session/permanent
suppression, denial-breaker addendum). Falls back to the existing
pending_approval behavior when no CLI callback is available.

Tests: 4 new cases in tests/tools/test_cli_approval_exec_ask_leak.py
mirroring the existing check_all_command_guards pair (approve/deny/timeout/
session-persistence). Mutation-verified: all 4 fail against the pre-fix code
and pass with it restored.

Neighbor suites: tests/tools/*approval* (291+ tests) and
tests/gateway/{test_approval_prompt_redaction,test_tui_approval_redaction,
test_plaintext_approval_routing,test_discord_exec_approval_content} +
tests/cli/test_cli_approval_ui.py all green. The 7 test_approval_mode_parity
/ test_nonrecursive_verification_artifact_cleanup failures seen in one full
batch run are pre-existing and independent of this change — confirmed by
re-running the identical batch with tools/approval.py stashed back to
pre-fix: the same 7 fail for the same reasons either way.

Note on an adjacent open PR: #65592 also touches check_execute_code_guard,
but an earlier, unrelated region of the function (adding an AST dangerous-
operation scanner to the "not is_gateway and not is_ask" auto-approve
branch). No semantic overlap with this fix's notify_cb-less branch; a small
rebase may be needed depending on merge order.
2026-08-20 01:16:25 +05:30
ethernet fdf6f1d4c8 feat(desktop): add an Appearance toggle that disables the intro splash
The wordmark and tagline on an empty chat had no off switch. Add an
Appearance row, Intro Splash, that hides it. The setting is on by
default, so the current experience does not change.

The splash is renderer chrome and no other Hermes surface can change
it, so the state stays local (localStorage) like the Chat Backdrop
toggle beside it. It does not mirror into gateway config.

Move the visibility condition out of the chat god-component into
shouldShowIntro(), next to the isRouteSessionMismatch() helper. The
tests prove that the toggle outranks every window and session clause:
off is off.
2026-08-19 15:22:49 -04:00
brooklyn! 78ffd71f14 Merge pull request #90205 from NousResearch/bb/windows-update-reexec-unattended
fix(update): run the Windows update hand-off unattended
2026-08-19 14:03:25 -05:00