fix(cli): harden hermes portal SystemExit handling + finish model-pick doc sweep
Self-review of #38465 surfaced three real items: 1. SystemExit escape (defense): `_login_nous` raises SystemExit(130)/(1) on cancel/failure. The logged-out login path inside `_model_flow_nous` catches it, but the expired-session re-login path (main.py) only catches Exception, so a Ctrl-C during re-auth could propagate past `_run_portal_one_shot` and kill the CLI. Add SystemExit to the portal handler so all cancel/abort cases end with the graceful 'Setup cancelled / retry later' message. 2. Doc sweep: the model-pick step was only added to the bare-`hermes portal` prose. Propagate it to the surfaces describing `hermes setup --portal` behavior that still omitted model selection: - `--portal` argparse help (main.py) - nous-portal.md intro + the numbered 'what it does' step list (EN + zh-Hans) - run-hermes-with-nous-portal.md 'default model after setup --portal' line, which was now contradictory (there's a picker, not a forced default) (EN + zh) 3. Test coverage: add parametrized regression test asserting the portal handler swallows KeyboardInterrupt / EOFError / SystemExit (returns None, no escape). Note on 'Skip (keep current)': delegating to _model_flow_nous means picking Skip preserves the prior provider instead of force-switching to nous — this is intentional and matches quick setup exactly; docs now say 'sets Nous as your provider (when you pick a model)' rather than unconditionally.
This commit is contained in:
@ -12681,9 +12681,9 @@ def main():
|
||||
setup_parser.add_argument(
|
||||
"--portal",
|
||||
action="store_true",
|
||||
help="One-shot Nous Portal setup: log in via OAuth, set Nous as the "
|
||||
"inference provider, and opt into the Tool Gateway. Skips the "
|
||||
"rest of the wizard.",
|
||||
help="One-shot Nous Portal setup: log in via OAuth, pick a Nous "
|
||||
"model, set Nous as the inference provider, and opt into the Tool "
|
||||
"Gateway. Skips the rest of the wizard.",
|
||||
)
|
||||
setup_parser.set_defaults(func=cmd_setup)
|
||||
|
||||
|
||||
@ -2770,7 +2770,12 @@ def _run_portal_one_shot(config: dict) -> None:
|
||||
from hermes_cli.main import _model_flow_nous
|
||||
|
||||
_model_flow_nous(config)
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
except (KeyboardInterrupt, EOFError, SystemExit):
|
||||
# _login_nous raises SystemExit(130)/(1) on cancel/failure; the
|
||||
# logged-out path inside _model_flow_nous catches it, but the
|
||||
# expired-session re-login path only catches Exception, so a
|
||||
# SystemExit there would otherwise escape and kill the whole CLI.
|
||||
# Treat all of these as a graceful cancel/abort for the portal flow.
|
||||
print()
|
||||
print_info(" Setup cancelled.")
|
||||
print_info(" You can retry later with `hermes portal`.")
|
||||
|
||||
@ -135,3 +135,23 @@ def test_one_shot_delegates_to_model_flow_nous(monkeypatch):
|
||||
"`hermes portal` must route through _model_flow_nous so the model "
|
||||
"picker runs every time (matching quick setup)."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exc", [KeyboardInterrupt, EOFError, SystemExit])
|
||||
def test_one_shot_swallows_cancel_and_systemexit(monkeypatch, exc):
|
||||
"""A cancel/abort from the delegated Nous flow must NOT escape and kill the
|
||||
CLI. `_login_nous` raises SystemExit(130)/(1) on cancel/failure, and the
|
||||
expired-session re-login path inside `_model_flow_nous` only catches
|
||||
Exception — so SystemExit could otherwise propagate out. The portal handler
|
||||
must treat KeyboardInterrupt/EOFError/SystemExit as a graceful cancel.
|
||||
"""
|
||||
import hermes_cli.setup as setup_mod
|
||||
|
||||
def boom(config):
|
||||
raise exc
|
||||
|
||||
monkeypatch.setattr("hermes_cli.main._model_flow_nous", boom)
|
||||
monkeypatch.setattr("hermes_cli.config.load_config", lambda: {})
|
||||
|
||||
# Must return normally (None), not propagate the exception.
|
||||
assert setup_mod._run_portal_one_shot({}) is None
|
||||
|
||||
@ -95,7 +95,7 @@ You should see Hermes call `web_search` (Firecrawl-backed, through the gateway)
|
||||
|
||||
## 5. Pick the model you actually want
|
||||
|
||||
The default after `hermes setup --portal` is a sensible general-purpose model, but the whole point of the subscription is access to the full catalog. Switch with `/model` mid-session:
|
||||
`hermes setup --portal` lets you pick a model during setup, but the whole point of the subscription is access to the full catalog — switch any time with `/model` mid-session:
|
||||
|
||||
```bash
|
||||
/model anthropic/claude-sonnet-4.6 # best general-purpose agentic
|
||||
|
||||
@ -14,7 +14,7 @@ If you only have time to set up one thing, set up this. The fastest path:
|
||||
hermes setup --portal
|
||||
```
|
||||
|
||||
That single command runs the Portal OAuth, sets Nous as your inference provider in `config.yaml`, and turns on the Tool Gateway. You're ready to `hermes chat` immediately after.
|
||||
That single command runs the Portal OAuth, lets you pick a Nous model, sets Nous as your inference provider in `config.yaml`, and turns on the Tool Gateway. You're ready to `hermes chat` immediately after.
|
||||
|
||||
Don't have a subscription yet? [portal.nousresearch.com/manage-subscription](https://portal.nousresearch.com/manage-subscription) — sign up, then come back and run the command above.
|
||||
|
||||
@ -99,9 +99,10 @@ This runs the full setup in one shot:
|
||||
|
||||
1. Opens your browser to portal.nousresearch.com for OAuth login
|
||||
2. Stores the refresh token at `~/.hermes/auth.json`
|
||||
3. Sets Nous as your inference provider in `~/.hermes/config.yaml`
|
||||
4. Turns on the Tool Gateway (web, image, TTS, browser routing)
|
||||
5. Returns you to your terminal ready to `hermes chat`
|
||||
3. Lets you pick a Nous model from the curated list (or skip to keep your current one)
|
||||
4. Sets Nous as your inference provider in `~/.hermes/config.yaml` (when you pick a model)
|
||||
5. Turns on the Tool Gateway (web, image, TTS, browser routing)
|
||||
6. Returns you to your terminal ready to `hermes chat`
|
||||
|
||||
If you don't have a subscription yet, sign up at [portal.nousresearch.com/manage-subscription](https://portal.nousresearch.com/manage-subscription) first.
|
||||
|
||||
|
||||
@ -95,7 +95,7 @@ Hey, search the web for "Hermes Agent release notes" and summarize the top 3 hit
|
||||
|
||||
## 5. 选择你实际需要的模型
|
||||
|
||||
`hermes setup --portal` 后的默认模型是一个合理的通用模型,但订阅的意义在于可以访问完整的模型目录。在会话中使用 `/model` 切换:
|
||||
`hermes setup --portal` 会在设置过程中让你选择模型,但订阅的意义在于可以访问完整的模型目录——随时可在会话中使用 `/model` 切换:
|
||||
|
||||
```bash
|
||||
/model anthropic/claude-sonnet-4.6 # 最佳通用 agentic 模型
|
||||
|
||||
@ -14,7 +14,7 @@ description: "一个订阅,300+ 前沿模型,Tool Gateway,以及 Nous Chat
|
||||
hermes setup --portal
|
||||
```
|
||||
|
||||
这条命令会完成 Portal OAuth 认证,在 `config.yaml` 中将 Nous 设为推理提供商,并开启 Tool Gateway。完成后即可立即运行 `hermes chat`。
|
||||
这条命令会完成 Portal OAuth 认证,让你选择一个 Nous 模型,在 `config.yaml` 中将 Nous 设为推理提供商,并开启 Tool Gateway。完成后即可立即运行 `hermes chat`。
|
||||
|
||||
还没有订阅?前往 [portal.nousresearch.com/manage-subscription](https://portal.nousresearch.com/manage-subscription) 注册,然后回来运行上面的命令。
|
||||
|
||||
@ -95,9 +95,10 @@ hermes setup --portal
|
||||
|
||||
1. 打开浏览器跳转至 portal.nousresearch.com 进行 OAuth 登录
|
||||
2. 将 refresh token 存储至 `~/.hermes/auth.json`
|
||||
3. 在 `~/.hermes/config.yaml` 中将 Nous 设为推理提供商
|
||||
4. 开启 Tool Gateway(网页、图像、TTS、浏览器路由)
|
||||
5. 返回终端,即可运行 `hermes chat`
|
||||
3. 让你从精选列表中选择一个 Nous 模型(也可跳过以保留当前模型)
|
||||
4. 在 `~/.hermes/config.yaml` 中将 Nous 设为推理提供商(当你选择模型时)
|
||||
5. 开启 Tool Gateway(网页、图像、TTS、浏览器路由)
|
||||
6. 返回终端,即可运行 `hermes chat`
|
||||
|
||||
如果还没有订阅,请先在 [portal.nousresearch.com/manage-subscription](https://portal.nousresearch.com/manage-subscription) 注册。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user