Commit Graph

14 Commits

Author SHA1 Message Date
teknium1 11576390fe refactor(model): one persist writer for /model across CLI, gateway, TUI, dashboard; ACP + dashboard validate through switch_model
One `/model --global` produced four config.yaml shapes. CLI wrote
default/provider/base_url/api_mode and cleared the context pin on a route
change; the gateway rewrote the whole `model:` block (whole-file save_config)
and only set api_mode for `custom`; the TUI wrote three keys and never
touched api_mode, so a switch off an Anthropic-wire endpoint left a stale
`api_mode: anthropic_messages` in config; the dashboard main slot had its own
switched-provider logic, wrote `base_url: ""` and always dropped
context_length. ACP `session/set_model` and `POST /api/model/set` accepted
any model string (parse_model_input + detect_provider_for_model) so a model
no catalog knows, or a provider with no credentials, was handed to the
session / persisted and only failed at inference time.

Canonical: `hermes_cli.model_switch.model_selection_config_updates` (the
shape) + `persist_model_selection(result, config_path=None)` (targeted
per-key `atomic_roundtrip_yaml_update` writes, so sibling
`model_slots`/`model_fallback` keys survive; explicit path for the
multiplexed gateway's profile config) + `apply_model_selection` (same shape
applied to an in-memory `model:` dict for callers that save a whole
document). `atomic_roundtrip_yaml_update(value=None)` now REMOVES the key
instead of writing `key: null`, so per-key and whole-document writers land
the same file. Shape = CLI/gateway semantics: default, provider, base_url
(cleared when the target has none), api_mode (cleared when unresolved),
context_length cleared only when `should_clear_context_pin` says the route
identity changed, inline api_key/api cleared for non-custom targets.

Sites -> canonical:
  hermes_cli/cli_model_switch_mixin.py::_persist_global_switch          -> deleted; _commit_model_switch calls persist_model_selection
  hermes_cli/cli_model_switch_mixin.py::_clear_persisted_context_for_model_switch -> deleted (folded into the shape)
  gateway/slash_commands_model.py::_persist_model_switch_to_config       -> to_thread forwarder: persist_model_selection(result, ctx.config_path)
  tui_gateway/model_switch.py::_persist_model_switch                     -> deleted; _apply_model_switch calls persist_model_selection
  hermes_cli/web_server_config.py::_apply_main_model_assignment          -> apply_model_selection(result) (+ explicit custom api_key)
  hermes_cli/web_server_config.py::_validated_main_model_selection       -> NEW: switch_model(--provider) gate; rejection -> HTTP 400
  hermes_cli/web_routers/{models,profiles,config_env}.py main-slot paths -> through _validated_main_model_selection
  acp_adapter/server.py::_resolve_model_selection                        -> deleted; _switch_model calls switch_model (provider:model -> --provider), rejection -> ValueError

Behavior changes: TUI --global now writes/clears model.api_mode and clears a
route-changed context pin; gateway --global no longer rewrites the whole
model block (sibling keys survive) and clears api_mode for every target;
dashboard main slot / profile-create model / custom-endpoint activate now
reject unknown/uncredentialed/unlisted models (HTTP 400) and persist the
resolved base_url/api_mode instead of `base_url: ""`; ACP rejects the same
(ValueError surfaced by the command/protocol handler). Gateway persist runs
on a worker thread against the routed profile's config_path (multiplex-safe).
Cleared keys are removed from config.yaml rather than left as `null`. ACP
still never persists.

Kept `_normalize_main_model_assignment`: switch_model rejects a vendor name
posing as a provider (`moonshotai` -> "Unknown provider"), so the
vendor->aggregator repair is not a duplicate; E2E verified both branches.
No config migration: readers already coalesce `base_url: ""` to absent
(`_config_base_url_for_provider`) and gate api_mode on provider match
(`_provider_supports_explicit_api_mode`), so no stale-shape reader bug.

Tests: tests/hermes_cli/test_model_persist_one_shape.py (four surfaces land
one block; same-route re-pick keeps the pin), tests/acp_adapter/
test_acp_dashboard_model_switch_validation.py (rejection + explicit
provider prefix). Replaces test_acp_set_model_explicit_provider.py and the
two TUI-only persist tests; tests that intercepted the old per-surface seams
(`cli.save_config_value`, `load_config_readonly`, `tui_gateway.server.
_persist_model_switch`) now intercept the canonical seam. Each fix
sabotage-verified red.
2026-09-13 05:21:02 -07:00
kshitijk4poor 8077206073 fix(cli): keep the no-stored-model early return ahead of the route read
CI: tests/cli/test_cli_resume_command.py builds bare HermesCLI objects without .model; the
refactor read self.model before the stored-model check the contributor's code made first.
2026-09-09 12:41:07 +05:30
kshitijk4poor 32273b8118 refactor(cli): one stored_session_route for interactive and one-shot resume
_apply_stored_session_runtime was a line-for-line copy of the first half of
_restore_session_model (stored-model guard, session_gateway_runtime, bare-custom heal,
model/provider-changed check). Extract that pure decision into
cli_model_switch_mixin.stored_session_route and have both resume paths call it; the
one-shot keeps only the _ModelChoice mapping and the drop-ambient-key rule.

main.py stops re-normalising `resume` — _resolve_chat_session_args already did.
Tests trimmed from 20 to 13: near-duplicate unit tests of the private helpers go, the
end-to-end _run_agent contracts (stored runtime + reopen; explicit --model wins) and the
empty-session-keeps-id case stay.
2026-09-09 12:41:07 +05:30
Teknium 7a33369e81 simplify(compat): interrupt — drop _ThreadAwareEventProxy/_interrupt_event legacy alias, repoint 2 test files
No runtime consumer read the proxy (terminal_tool/environments call is_interrupted()/set_interrupt()
directly); its only users were tests patching tools.interrupt._interrupt_event, which had no effect on
the code under test. tools/terminal_tool.py's own re-export of the name is owned by another worker.
2026-09-03 14:00:59 -07:00
Teknium c93ace77c2 simplify(compat): config/runtime_provider/plugins/commands/secrets_cli/kanban — drop 96 re-exports (incl. PEP 562 facades) + 3 aliases (get_pre_tool_call_directive/_block_message, get_telegram_handler_factories), repoint 56 callers + 50 test files 2026-09-03 14:00:17 -07:00
Teknium e83816a4d1 review-fix(comments): restore lost #NNNN rationale comments across non-test source (mechanical sweep, condensed, code unchanged)
For each issue anchor present in BASE 63279301bc non-test .py and absent on HEAD, the BASE comment/docstring block was re-attached at the HEAD location of the code it explained (matched by the distinctive code line / enclosing def). Sentences already covered by an existing HEAD comment were deduped; the issue number always survives. Insert-only: no code lines changed.
2026-09-03 09:44:26 -07:00
Teknium 3494f7cf23 refactor(hermes_cli): AST-neutral closer hugging across r3-17 slice files 2026-09-02 23:58:31 -07:00
Teknium 670b974bed refactor(hermes_cli): compact mixin docstrings/comments by hand (keep every WHY), drop intra-body blank padding 2026-09-02 21:30:29 -07:00
Teknium 165bcb705d refactor(hermes_cli): collapse announce-gated wake prints, provider-family normalize helper, stream boundary predicate 2026-09-02 21:13:10 -07:00
Teknium c95a56be06 refactor(hermes_cli): fold picker/typed /model commit into _commit_model_switch + shared switch/dispatch helpers 2026-09-02 21:06:11 -07:00
Teknium 5ddca91a31 refactor(hermes_cli): AST-neutral bracket hug/pack layout pass on g5-model files 2026-09-02 20:56:03 -07:00
Teknium b39f0337f1 refactor(hermes_cli): unify /model switch commit path (summary, global persist, preflight, runtime-field snapshot) 2026-09-02 20:26:45 -07:00
Teknium 79aeb22886 refactor(cli): unify duplicated model-switch staging and last-assistant-text blocks
cli_model_switch_mixin: the identical snapshot->stage->agent.switch_model->rollback
block in _apply_model_switch_result and _confirm_and_apply_cli_model_switch (2x47
lines) -> _stage_and_swap_model(result, old_model) -> bool. cli_loops_mixin: the
identical reversed-history assistant-text extraction in the loop-tick and goal
post-turn hooks -> _last_assistant_response_text(). Four _StubCLI test doubles bind
the new helper to the real implementation.
2026-09-02 16:17:31 -07:00
Teknium eb74a00c71 refactor(cli): split HermesCLI into 10 cohesive mixins (cli.py 22,284 -> 9,150)
326 methods lifted by AST (bodies identical; ast.dump-verified) into
hermes_cli/cli_{tui,status_bar,voice,model_switch,session,stream,modal,
terminal,info,loops}_mixin.py. cli.py-internal symbols resolve via lazy
'from cli import ...' inside each method (no import cycle; patch('cli.X')
keeps working). The three 'global' writers (_skill_commands, _cli_wake_owner)
now write the cli module attribute explicitly so the origin's readers still
see them. Dropped imports left unused in cli.py; kept display_hermes_home /
build_welcome_banner as re-exports (mixins + tests resolve them via cli).
Repointed two AST change-detector tests to cli_tui_mixin.py; one test
fixture now keeps 'cli' in sys.modules across its patch.dict scope.
2026-09-02 15:42:24 -07:00