Commit graph

1975 commits

Author SHA1 Message Date
Teknium
7c954969b7
fix(auxiliary): route direct-create aux callers through call_llm (#65029)
* fix(auxiliary): route direct-create aux callers through call_llm (#35566)

Five callers (kanban_decompose, kanban_specify, profile_describer, and
goals.py's judge + draft-contract) built raw clients via
get_text_auxiliary_client() and passed extra_body=get_auxiliary_extra_body()
— which only returns Nous portal tags and ignores
auxiliary.<task>.extra_body from config.yaml entirely. That was the
remaining half of #35566 after the call_llm path was fixed.

Routing them through call_llm(task=...) gives each caller the full
auxiliary contract for free: task extra_body, the reasoning_effort
shorthand, transient retries, provider-profile projection, and fallback
chains. goal_judge gains a DEFAULT_CONFIG block (it had none — its
provider/model overrides silently didn't exist as documented keys).

get_auxiliary_extra_body() now has zero non-test callers; kept for
plugin back-compat.

Fixes #35566.

* test: migrate kanban dashboard + CLI specify mocks to call_llm

Two more consumers of specify_task mocked the old
get_text_auxiliary_client symbol (missed in the first sibling sweep —
they live outside tests/hermes_cli's kanban files): the dashboard
plugin's /specify endpoint tests and the /kanban slash-command E2E.
Same migration as the rest: mock call_llm at the source, no-provider
now surfaces via the LLM-error branch.
2026-07-15 07:39:17 -07:00
Teknium
9df5f879b4 feat(mcp): enforce exact version pins across the whole MCP catalog
Catalog entries now follow the same supply-chain rules as pyproject
dependencies:

- n8n: install.ref main -> full commit SHA 7a9ae007 (2026-05-23,
  branches/tags can be moved by the upstream owner; SHAs cannot)
- new contract test: every shipped manifest must pin exactly —
  git installs need a 40-char SHA, uvx/npx-style launchers need
  pkg==X / pkg@X with a digit-leading version (rejects bare names,
  ranges, and npm dist-tags like @latest)
- module docstring documents the pin policy (exact version, 2-week
  cooldown)

unreal-engine and linear are http transports (server runs elsewhere)
so there is nothing to pin at the transport layer.

Verified: unpinning blender-mcp in the manifest makes the contract
test fail with a named diagnostic; restoring the pin passes.
2026-07-15 04:56:25 -07:00
Teknium
9be941dac1 feat(mcp): add Blender to the MCP catalog with a curated 4-tool default
Adds optional-mcps/blender (ahujasid/blender-mcp, stdio via uvx). The
server advertises 22 tools; 18 front optional asset services with no
upstream trim mechanism, so tools.default_enabled pins the install to
the core surface (scene/object info, viewport screenshot, code exec)
and the rest stay opt-in through 'hermes mcp configure blender'.

Manifests can now declare transport.env (static, non-secret subprocess
env vars), parsed/validated in _parse_manifest and written by
_build_server_config — used here to ship DISABLE_TELEMETRY=true per
the no-telemetry-without-opt-in policy. Runtime already honored
per-server env; manifests just couldn't declare it.
2026-07-15 04:56:25 -07:00
HexLab98
1b059d1ae7 test(config): regression for merge_existing partial saves (#62723) 2026-07-15 04:26:20 -07:00
Teknium
b34e565957 feat(models): catalog-labeled silent default — GLM-5.2 marked "default": true in the model catalog
The remote model catalog (website/static/api/model-catalog.json) now labels
exactly one entry per provider block with "default": true — z-ai/glm-5.2 for
both OpenRouter and Nous Portal. That labeled entry is the model Hermes
silently lands on when the user never picked one, and it can be rotated by
editing the manifest alone: no release needed.

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

E2E (temp HERMES_HOME): fresh-install constant fallback, shipped-manifest
label read, release-free rotation (relabeled cache -> new default across
models.py, tui_gateway, and gateway empty-model paths) all verified.
2026-07-15 00:10:31 -07:00
Justin Schille
3dca75b45c feat(moa): support per-slot reasoning effort 2026-07-14 21:08:22 -07:00
kshitijk4poor
4554fe128a fix(slack): complete agent view workspace routing 2026-07-14 13:58:36 -07:00
Edison42
9a3b676fed feat(slack): support agent view manifests 2026-07-14 13:58:36 -07:00
墨綠BG
bdb1c87247 fix(dashboard): pass backup output with -o 2026-07-14 13:55:18 -07:00
kshitijk4poor
1f41bdbecd fix(upstage): collapse unknown future efforts to high; behavior-contract tests
Review findings from the 4-angle pass:
- Unknown-but-enabled effort levels now collapse to Solar's strongest
  (high) instead of silently downgrading to the medium default — guards
  against the next #62650-style vocabulary addition. Explicit-empty
  effort keeps the medium default.
- fallback_models test now asserts the behavior contract (non-empty, no
  denied families) instead of freezing the exact model tuple
  (change-detector, AGENTS.md reject reason).
- Drop unused pytest import in test_upstage_provider.py.
2026-07-15 00:09:24 +05:30
Changhyun Min
c1e36f4329 fix(agent): register Upstage keys in the env-var catalog
`UPSTAGE_API_KEY` / `UPSTAGE_BASE_URL` were wired through the provider
resolver, auth registry, and the EnvPage grouping, but never added to
`OPTIONAL_ENV_VARS` in hermes_cli/config.py. The dashboard/desktop
Providers page builds its list from that catalog (`/api/env` iterates
`OPTIONAL_ENV_VARS`), so with no entry the keys were never emitted and
"Upstage Solar" never rendered — the EnvPage prefix group stayed empty.

Add both keys under `category: "provider"` (matching gmi/minimax) so they
show up in `hermes dashboard` / `hermes desktop` under "Upstage Solar".
Adds a regression test asserting the catalog contains them, mirroring the
existing GMI coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 00:09:24 +05:30
Changhyun Min
20502b407c feat(agent): add Upstage Solar as a model provider
Adds Upstage Solar as a bundled model-provider plugin. Solar exposes an
OpenAI-compatible chat-completions endpoint at https://api.upstage.ai/v1, so
the generic chat_completions transport handles request/response/streaming/tool
calls — the profile is the core integration.

Provider registration (Upstage isn't in models.dev, so each registry that does
not auto-wire from the plugin layer needs an explicit entry — same pattern as
nvidia/gmi):
- plugins/model-providers/upstage/: UpstageProfile + plugin.yaml. Picker default
  and offline catalog list only the agentic Solar Pro models, led by `solar-pro`
  (rolling alias for the latest Pro). default_aux_model empty so aux tasks use
  the main model. `solar` alias. UPSTAGE_BASE_URL overrides the host.
- hermes_cli/providers.py: HERMES_OVERLAYS + label + `solar` alias, so
  resolve_provider_full('upstage') resolves (without this, an explicit
  `provider: upstage` in config was dropped and fell through to auto-detect).
- hermes_cli/auth.py: PROVIDER_REGISTRY entry + `solar` alias, so `hermes
  doctor` / resolve_provider recognise upstage (the static-registry path the
  lazy profile-extension doesn't reliably cover at validation time).
- hermes_cli/models.py: CANONICAL_PROVIDERS entry places Upstage Solar in the
  curated picker order (above the auto-appended `custom`).
- agent/model_metadata.py: context-window fallbacks (/v1/models omits
  context_length); `solar-pro` carries the 128K Pro context as the catch-all.

Reasoning: UpstageProfile.build_api_kwargs_extras wires Solar's top-level
`reasoning_effort` (low|medium|high; xhigh/max→high). Reasoning-capable families
are solar-pro* and solar-open*; solar-mini/syn-pro never receive it. Defaults ON
at medium when unset (matches the /reasoning "medium (default)" label);
`/reasoning none` disables; explicit/saved settings are honored. No
reasoning_content echo handling needed (unlike DeepSeek/Kimi).

Web dashboard:
- web/src/pages/EnvPage.tsx: add an "Upstage Solar" provider group so
  UPSTAGE_API_KEY / UPSTAGE_BASE_URL appear under LLM Providers (not "Other").

Docs/tests:
- .env.example: documents UPSTAGE_API_KEY / UPSTAGE_BASE_URL.
- tests: profile wiring, reasoning_effort mapping (pro/open/mini, efforts,
  disabled, default-on), provider-resolver regression (resolve_provider_full /
  get_provider / solar alias / overlay), `solar-pro` default.

Testing: pytest tests/providers tests/plugins/model_providers
tests/hermes_cli/test_upstage_provider.py tests/run_agent/test_provider_parity.py
tests/hermes_cli/test_api_key_providers.py; ruff clean. Verified end-to-end:
`hermes doctor` shows "Upstage Solar", and live chat works via both
`--provider upstage` and `--provider solar`. Reasoning wire format per
https://console.upstage.ai/api/docs/for-agents/raw. Platforms tested: macOS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 00:09:24 +05:30
mark
96a0708448 fix(auth): preserve provider fallback during refresh 2026-07-14 07:02:05 -07:00
mark
f9e35e6e94 fix(auth): route session refresh with provider hint cookie 2026-07-14 07:02:05 -07:00
kshitijk4poor
71e91f89b5 test(update): document shared npm cache scope 2026-07-14 16:58:26 +05:30
kshitijk4poor
d426b9ddfe fix: derive skip-key manifests from npm workspaces config
Review round 2 from @ethernet8023 on #61580:

1. The manifest list was a hardcoded root/ui-tui/web trio — desktop and
   any future workspace escaped the skip key even though step 1's root
   install hoists deps for every workspace. The list is now expanded
   from the root package.json 'workspaces' globs (npm's own source of
   truth): on the real repo that yields all 8 manifests incl.
   apps/desktop, apps/bootstrap-installer, apps/shared, and the nested
   ui-tui/packages/hermes-ink. Unreadable package.json falls back to
   root manifests only (never skips more than main would install).

2. --prefer-offline dropped entirely (this branch no longer carries
   #39399): local 3-run benchmarks on the repo's real manifests show
   the flag is noise on npm ci with a warm cache (root: 0.90s vs 0.84s
   avg; ws: 4.02s vs 4.00s avg) — npm ci does no resolution and the
   content-addressed cache already serves tarballs locally. It also
   carried the stale-resolution risk on the npm install fallback the
   reviewer flagged. All the real win is the skip itself (0s vs ~5s+).

Tests: workspace-glob edit (desktop), literal-listed edit, and
new-workspace-under-glob all defeat the skip; verified against the
real repo's workspace config (8 manifests picked up).
2026-07-14 16:58:26 +05:30
Rod Boev
aa56243a89 perf(cli): skip npm install during update when lockfile is unchanged (#17268)
(cherry picked from commit 8fb6d5e910)
(cherry picked from commit 27474007b9463d7ce19d981a24aeeac552e79f48)
2026-07-14 16:58:26 +05:30
slow4cyl
452861fdc1 review follow-up: violation-only retry streak with defined max_retries precedence
Address the hermes-sweeper review of #61233: the bounded retry budget
is now a clean-exit-specific streak, not a share of the unified
consecutive_failures counter.

- detect_crashed_workers stamps a protocol_violation marker into the
  violation run's metadata (via the event payload _end_run copies);
  _protocol_violation_streak derives the streak from run history:
  consecutive most-recent violation runs, rate_limited runs neutral
  (mirroring their unified-counter treatment), any other closed run
  resets it. Mixed failure kinds can neither consume nor extend the
  budget.
- Below-budget violations no longer call _record_task_failure at all:
  the task returns to ready with last_failure_error stamped directly
  (including the corrective retry guidance wording adopted from #61817,
  which build_worker_context surfaces to the retry worker) and the
  unified counter is untouched, keeping the two budgets independent.
- At the bound the trip funnels through _record_task_failure with a new
  keyword-only force_trip=True: the reaper has already resolved the
  per-task max_retries override against the violation streak itself, so
  the threshold comparison is skipped rather than double-applied.
  max_retries keeps its documented top precedence in both directions:
  max_retries=1 blocks on the first violation, max_retries=5 blocks on
  the fifth consecutive one, unset uses the default bound of 3.
- Replace the first-violation-blocks regression test with five tests:
  first occurrence retries (ready + guidance stamped + no gave_up +
  unified counter untouched); streak trips exactly at the bound with
  protocol_violations/protocol_violation_limit in the gave_up payload
  and the auto-blocked side channel set; a prior nonzero crash does not
  consume the violation budget; a non-violation failure between
  violations resets the streak; max_retries precedence both directions.
  All five fail against the previously reviewed diff and pass with this
  follow-up. The test harness resolves hermes_cli.kanban_db fresh and
  uses that single module object for the exit registry, liveness patch,
  and reaper — earlier suite tests reload the module, and the old
  mixed-object harness made _classify_worker_exit return unknown (the
  reason the old test failed in full-suite runs on main).

Kanban suite: zero introduced failures vs upstream/main tip (62 vs 63
pre-existing environmental failures — the one no longer failing is the
old violation test this replaces; 662 passed vs 657).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 16:47:33 +05:30
Teknium
0d3ad193d6
fix(tests): patch catalog urlopen wrapper in gemini probe tests (#64318)
test_probe_sends_client_context_to_gemini and
test_probe_omits_gemini_client_context_for_other_providers (added in
b8eb89f5c) patch hermes_cli.models.urllib.request.urlopen, but
probe_api_models routes requests through the
_urlopen_model_catalog_request wrapper (open_credentialed_url from the
urllib_security hardening), so the mock is never invoked and
mock_urlopen.call_args is None -> TypeError. Every CI run on main and
every PR has been failing test slice 7/8 on these two tests.

Point the patches at _urlopen_model_catalog_request, the same target
every sibling test in TestProbeApiModelsUserAgent already uses.
89/89 tests in the file now pass.
2026-07-14 03:11:19 -07:00
Vishal Dharmadhikari
b8eb89f5c9 feat(gemini): improve request context for support and compatibility
Include the Hermes client name and version with Gemini inference, model and tier checks, and TTS requests. Add focused coverage for the request headers and keep the Gemini-specific context scoped to Google Gemini endpoints.
2026-07-13 22:26:19 -07:00
Erosika
c7e09f2571 fix(desktop): restore curated declared schema for the provider panel
The desktop provider panel previously rendered the curated declarations
from hermes_cli/memory_providers.py: five hindsight fields, and no panel
at all for undeclared providers like honcho (OAuth connect only). The
dashboard provider-switching rework re-pointed the shared config route
at raw plugin schemas, so the desktop began dumping every internal field
(35 for hindsight) and grew a bespoke honcho panel.

Serve both surfaces from the same route: ?surface=declared returns the
curated schema (empty for undeclared providers) with the original
config-file + env-store write semantics; the dashboard keeps the raw
plugin schema unchanged. The desktop client opts into declared.
2026-07-13 21:01:55 -07:00
Teknium
861d69c7bb
fix(dashboard): keep memory.provider in the config schema so Desktop's dropdown survives (#63886)
The dashboard's dedicated memory-provider UI (4b184cbe5) excluded
memory.provider from /api/config/schema server-side. Desktop's settings
page builds its field list from that schema, so the Memory Provider
dropdown silently vanished from Desktop after v0.18.1.

- web_server.py: restore memory.provider as a select in _SCHEMA_OVERRIDES,
  with options built from plugins.memory discovery (was a stale hardcoded
  [builtin, honcho] list before the removal)
- plugins/memory: add list_memory_provider_names() — directory-scan-only
  name listing, safe at module import time (no provider imports)
- web ConfigPage: hide memory.provider client-side instead — the Plugins
  page owns the dedicated provider-switching UI there
- tests: schema contract (select present, category memory, builtin
  sentinel) + invariant that every discoverable provider is selectable
2026-07-13 18:56:51 -07:00
kshitijk4poor
10dc1571bc fix(deepinfra): align refresh and TTS availability
Forward explicit catalog refreshes and make the TTS availability gate follow the configured provider instead of unrelated credentials.
2026-07-14 02:59:39 +05:30
kshitijk4poor
2fc3f9c1ff fix(deepinfra): harden multimodal provider routing
Prevent credential forwarding across catalog redirects, retain explicit opt-in semantics for paid media backends, fail closed on invalid provider configuration, avoid mixed-catalog and output-limit assumptions, and reserve native STT provider names.
2026-07-14 02:59:39 +05:30
Georgi Atsev
fe002eb124 feat(providers): Support DeepInfra as an LLM provider 2026-07-14 02:59:39 +05:30
kshitijk4poor
ac705b52c9 fix(sessions): validate imported session payloads
Reject metadata that would make session queries fail, bound import work, and detach cyclic lineage links. Guard lineage traversal against pre-existing corrupt cycles.
2026-07-13 15:41:00 +05:30
Shannon Sands
b51d365ef0 feat(dashboard): add session import flow 2026-07-13 15:41:00 +05:30
liuhao1024
e4ea0a0ed7 fix(config): preserve string-typed config values 2026-07-12 23:44:16 -07:00
Teknium
8030b01a2a fix(kanban): harden durable artifact handoff 2026-07-12 23:09:41 -07:00
Kevin Yin
e6c42b5d80 fix(kanban): preserve scratch completion artifacts 2026-07-12 23:09:41 -07:00
Robert Blaas
da6d6164ba fix(dashboard): correct approvals.mode select options
The web UI CONFIG_SCHEMA showed ['ask', 'yolo', 'deny'] for the
approvals.mode select field. These don't match any real config values
and 'smart' mode was entirely unreachable from the dashboard.

Correct the options to ['manual', 'smart', 'off'] which match the
values defined and documented in hermes_cli/config.py.

Adds a regression test to TestBuildSchemaFromConfig to pin the correct
option names and guard against future drift.

Fixes #31925
2026-07-12 17:42:42 -07:00
Teknium
9a15fad0d6
fix(web): preserve declared providers in model writes (#63058)
Unify the named-provider fixes from #52506, #57185, #60337, and #60901 at the main-model normalization chokepoint.

Co-authored-by: izumi0uu <izumi0uu@gmail.com>
Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
Co-authored-by: Paulo Henrique <paulohenrique_789@hotmail.com>
2026-07-12 03:08:08 -07:00
Teknium
2b5d4ae916
fix(model): merge configured models into picker rows (#63055)
Preserve the root cause and precedence direction from #43538 while applying the merge before truncation and covering all declared model shapes.

Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
2026-07-12 03:05:12 -07:00
teknium1
8c77206859 refactor(model): gate picker rows by runtime capability 2026-07-12 03:04:07 -07:00
teknium1
6503f36ab9 test(model): simplify routable picker invariants 2026-07-12 03:04:07 -07:00
Ahmett101
938c2622f6 fix(model_switch): filter /model picker for unregistered providers (#57503)
list_authenticated_providers() emits picker rows for every slug in
PROVIDER_TO_MODELS_DEV that has any credential env-var set. Several of
those slugs (notably 'mistral') have no PROVIDER_REGISTRY entry, so
resolve_provider() rejects them as 'Unknown provider' once the user
selects a model — leaving the picker showing rows that cannot actually
be selected.

Add a resolve-gate in section 1: if PROVIDER_REGISTRY.get(hermes_id)
is None, skip the slug. The picker now only lists providers that can
actually be switched to at runtime.

This automatically resolves the duplicate-Mistral dedup symptom too:
once the broken-from-models.dev row is filtered, the conflict between
PROVIDER_TO_MODELS_DEV['mistral'] and a custom_providers 'Mistral' row
is moot.

Composes with #50289 (which promotes mistral to first-class via the
provider-plugin path): when that lands, PROVIDER_REGISTRY gains a
'mistral' entry and the gate becomes a no-op for it. No conflict.

Tests (regression suite):
- tests/hermes_cli/test_model_switch_filter_unresolved.py (new, 4 tests):
  Picker excludes 'mistral' when MISTRAL_API_KEY is set; 'deepseek' and
  'xai' (PROVIDER_REGISTRY-backed) still appear; 'mistral' stays
  excluded when no key is set. Confirmed by reverting the fix and
  seeing the test fail with 'mistral leaked into /model picker'.

Cross-checked against the existing 51 test_model_switch_* and
test_custom_provider_* cases — 55/55 PASS, no regressions.
2026-07-12 03:04:07 -07:00
teknium1
1e75744b79 refactor(model): centralize picker credential availability 2026-07-12 02:59:14 -07:00
AIalliAI
3a67a7be55 fix(model-switch): don't treat an exhausted credential pool as authenticated
An aggregator whose pooled credentials are all exhausted/dead still counted as
an authenticated provider during no-provider /model resolution. It then won the
model-name match, was set as the sticky session provider, and poisoned every
later switch with "empty API key" errors while still routing through the dead
aggregator.

list_authenticated_providers now requires a pool to have at least one available
entry (has_available, not has_credentials / bare key presence) at all three
credential-pool gates. Simple token-style entries that don't parse into
exhaustion-tracked entries keep the prior behaviour, so providers whose creds
live only in the auth-store credential_pool still appear.

Fixes #45759
2026-07-12 02:59:14 -07:00
Teknium
7550c594ce
feat(reasoning): add max and ultra effort levels (#62650) 2026-07-12 00:26:49 -07:00
teknium1
31152ae108 fix(providers): align Fireworks integration with project policy 2026-07-11 05:43:35 -07:00
Alex Jestin Taylor
c97d9a4c07 feat(providers): add Fireworks AI as preferred provider
Bundle Fireworks AI as a first-class BYOK provider across the CLI, web/TUI,
and desktop onboarding.

- New model-provider plugin with attribution headers (HTTP-Referer / X-Title)
  so Fireworks can attribute Hermes traffic; PAYG-safe default aux + fallback
  models (accounts/fireworks/models/...), IDs tracking fw-ai/fireconnect.
- Registered in CANONICAL_PROVIDERS so it appears in the CLI/web/TUI pickers.
- Alias wiring (fireworks-ai, fw) into both CLI resolvers.
- First-class wiring: OPTIONAL_ENV_VARS, HERMES_OVERLAYS (FIREWORKS_BASE_URL
  override), doctor env hints. Live catalog + model_metadata are auto-derived.
- doctor: treat Fireworks' native slash-form IDs (accounts/fireworks/...) as
  valid, not aggregator vendor prefixes, so it no longer tells Fireworks users
  to switch to openrouter or drop the prefix.
- picker: plugin providers with no static curated list now lead with their
  profile fallback_models, so the default is an agentic chat model instead of
  whatever the live catalog returns first (Fireworks listed an image model,
  flux-*, ahead of its chat models).
- Desktop onboarding: Fireworks as a RECOMMENDED hero card with the official
  Fireworks logomark and a brand-purple badge, routing to the BYOK key form;
  i18n in en/ja/zh/zh-hant.
- Tests: profile contract, first-class wiring (both resolvers, overlay, config,
  doctor incl. the slash-form regression, aux headers, credentials), discovery
  spot-check, and a live smoke test driven through the Hermes runtime.

Fire Pass (fpk_) support is coming soon; the future wiring is kept as a
commented-out scaffold in the plugin.
2026-07-11 05:43:35 -07:00
teknium1
8041be7954 fix(model): keep configured provider authoritative 2026-07-11 05:43:07 -07:00
konsisumer
0ca6a98541 fix(cli): keep current provider visible in model pickers 2026-07-11 05:43:07 -07:00
briandevans
5ecc07986f fix(cli): preserve -t/-m/--provider/--tui/--dev before chat subcommand
`hermes -t web chat` silently dropped the toolset filter (and the same
hold true for `-m`, `--provider`, `--tui`, `--dev` placed before
`chat`). Reported in #28780 for `-t/--toolsets`; the others are sibling
failures with the same root cause.

Root cause: the chat subparser re-declared these flags with `default=None`
(or `default=False` for store_true) on top of the matching top-level
parser flags. When argparse dispatches into the subparser it shares the
namespace via `dest`, so the subparser's default overwrites whatever the
top-level parser parsed before the subcommand. `-s/--skills`, `-r/-c/-w`,
`--yolo`, and `--pass-session-id` already use `default=argparse.SUPPRESS`
for exactly this reason — the chat-subparser action becomes a no-op
unless the user explicitly passes the flag after `chat`, and the parent
value survives.

Reproduction (origin/main, before fix):

  >>> parser.parse_known_args(["-t", "web", "chat"]).toolsets
  None
  >>> parser.parse_known_args(["chat", "-t", "web"]).toolsets
  'web'

After fix:

  >>> parser.parse_known_args(["-t", "web", "chat"]).toolsets
  'web'
  >>> parser.parse_known_args(["chat", "-t", "web"]).toolsets
  'web'

Sibling flags fixed in the same commit because they share the exact same
argparse pattern bug — verified via a new contract test that scans every
chat-subparser action whose `dest` is also on the top-level parser and
asserts `default is argparse.SUPPRESS`. The test fails on origin/main
listing all five offenders and passes after this fix.

Test additions in tests/hermes_cli/test_argparse_flag_propagation.py:
- TestChatSubparserInheritedValueFlags exercising real `_parser` build
  (not the hand-rolled replica) so it catches future drift.
- Parametrized before-chat / after-chat cases for `-t`, `--toolsets`,
  `-m`, `--model`, `--provider`.
- Negative case: passing none of the flags leaves attrs at the top-level
  parser's `None` default (SUPPRESS does not remove existing attrs).
- Combined case: all three value flags before `chat` simultaneously.
- store_true cases for `--tui` / `--dev`.
- Contract test asserting every shared-`dest` flag on chat uses SUPPRESS.

Fixes #28780.
2026-07-11 03:51:43 -07:00
kshitijk4poor
3b2ef789df test(models): patch secured Novita pricing seam 2026-07-11 12:28:55 +05:30
kshitijk4poor
d83cd6f7c3 fix(security): secure Azure catalog probes 2026-07-11 12:28:55 +05:30
kshitijk4poor
1f46145e03 fix(security): order sanitizer after installed hooks 2026-07-11 12:28:55 +05:30
kshitijk4poor
92c2146039 fix(security): sanitize after installed request hooks 2026-07-11 12:28:55 +05:30
kshitijk4poor
4530a4ca4c fix(security): preserve opener-level header policy 2026-07-11 12:28:55 +05:30
kshitijk4poor
cf34a1e8c7 fix(security): cover remaining catalog credential paths 2026-07-11 12:28:55 +05:30