mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
fix(desktop): Windows browser-setup journey — console flash, idempotent setup, Nous Portal activation (#67473)
* fix(windows): suppress console-window flash in tools post-setup subprocess spawns
The desktop GUI runs post-setup hooks via a detached, console-less
'hermes tools post-setup <key>' child (spawned with windows_detach_flags).
But the hook implementations in tools_config.py ran their inner installers
(npm install, agent-browser install, uv/pip installs, ensurepip, cua-driver
version probes and installer) without Windows creationflags — and on
Windows a console-less parent spawning a console/.cmd child materializes a
brand-new console window, the 'terminal flash' reported on the
Capabilities > Browser Automation setup journey.
Add _post_setup_no_window_flags(), a local wrapper around
windows_hide_flags() (CREATE_NO_WINDOW only — DETACHED_PROCESS would sever
stdio and break capture_output), and pass it at every post-setup subprocess
call site. Spawns that stream live output to the user's console
(verbose cua-driver install) only hide when stdout is not a tty, so
interactive CLI installs keep their output. POSIX behavior is unchanged
(the helper returns 0 off-Windows).
* fix(desktop): make Capabilities post-setup idempotent — Installed state instead of unconditional Run setup
The GUI panel rendered the primary 'Run setup' CTA whenever a provider
declared post_setup, ignoring the server-computed readiness status the
config endpoint already serves. Users on Windows clicked 'Run setup' on
an already-installed Local Browser and watched it 'install' again.
Frontend: PostSetupRunner now takes installed (provider.status === 'ready')
and renders an 'Installed' pill + small 'Re-run setup' text button in that
state; onComplete still refetches the toolset config, so a fresh install
flips the row to Installed once the endpoint reports ready.
Backend:
- _POST_SETUP_READY extended: agent_browser now tracks the FULL local
install (_local_browser_runnable: CLI + Chromium-or-Lightpanda) instead
of the bare CLI check; new entries for the cloud 'browserbase' hook
(CLI only — cloud rows host their own Chromium) and camofox (npm
package present).
- _run_post_setup prints distinct 'already installed, nothing to do'
messages for the agent-browser/Chromium/Camofox early-exits so the GUI
action log tells the truth on re-runs vs fresh installs.
i18n: new postSetupInstalled/postSetupRerun/postSetupInstalledHint strings
in en, ja, zh, zh-hant + types.
* fix(desktop): let managed Nous Subscription rows activate from the GUI via the Portal sign-in flow
PUT /api/tools/toolsets/{name}/provider intentionally skips the Nous
Portal auth gate the CLI runs inline (ensure_nous_portal_access) — but no
desktop surface handled it. Selecting 'Nous Subscription (Browser Use
cloud)' from Capabilities wrote browser.cloud_provider=browser-use +
use_gateway=true and then silently never activated: _is_provider_active
requires feature.managed_by_nous, which stays false without the
entitlement, and the credential was never used.
Backend: after apply_provider_selection, the endpoint now checks the
managed row's entitlement (get_nous_subscription_features force_fresh +
the same per-category coverage gate the CLI applies) and reports the gap
with additive response fields {needs_nous_auth: true, feature}. The
selection is still persisted — activation is what's gated.
Frontend: handleSelect surfaces a 'Sign in to Nous Portal' warning toast
with a Sign-in action instead of the misleading success toast. The action
drives the EXISTING Nous Portal OAuth device-code flow (provider id
'nous' in _OAUTH_PROVIDER_CATALOG): POST /api/providers/oauth/nous/start,
open verification_url, poll /poll/{session}; on approval the panel
refetches the toolset config so is_active/status flip.
i18n: nousAuthNeeded*/nousAuthSignIn/nousAuthDone*/nousAuthFailed strings
in en, ja, zh, zh-hant + types.
This commit is contained in:
parent
09109fec98
commit
aa1ad32191
12 changed files with 719 additions and 24 deletions
|
|
@ -14763,10 +14763,26 @@ async def select_toolset_provider(
|
|||
write identical config keys (``web.backend``, ``tts.provider``, etc.).
|
||||
API keys and post-setup flows are handled by separate endpoints. Returns
|
||||
400 for unknown toolset or provider names.
|
||||
|
||||
Managed Nous rows (``managed_nous_feature``) additionally report the
|
||||
Portal entitlement state: the CLI flow gates these selections on
|
||||
``ensure_nous_portal_access`` (inline login), but the GUI has no inline
|
||||
prompt, so selecting one while logged out / unentitled used to write the
|
||||
config keys and then never activate (``_is_provider_active`` requires
|
||||
``managed_by_nous``). The response now carries an additive
|
||||
``needs_nous_auth: true`` + ``feature`` so the client can drive the
|
||||
existing Nous Portal OAuth flow (``POST /api/providers/oauth/nous/start``)
|
||||
and refetch.
|
||||
"""
|
||||
from hermes_cli.tools_config import (
|
||||
TOOL_CATEGORIES,
|
||||
apply_provider_selection,
|
||||
_get_effective_configurable_toolsets,
|
||||
_visible_providers,
|
||||
)
|
||||
from hermes_cli.nous_subscription import (
|
||||
MANAGED_FEATURE_COVERAGE_CATEGORY,
|
||||
get_nous_subscription_features,
|
||||
)
|
||||
|
||||
valid = {ts_key for ts_key, _, _ in _get_effective_configurable_toolsets()}
|
||||
|
|
@ -14780,7 +14796,40 @@ async def select_toolset_provider(
|
|||
except KeyError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc).strip('"'))
|
||||
save_config(config)
|
||||
return {"ok": True, "name": name, "provider": body.provider}
|
||||
|
||||
response: Dict[str, Any] = {"ok": True, "name": name, "provider": body.provider}
|
||||
|
||||
# Entitlement check for managed Nous rows — mirrors the gate the CLI
|
||||
# applies via ensure_nous_portal_access at selection time.
|
||||
cat = TOOL_CATEGORIES.get(name)
|
||||
row = None
|
||||
if cat:
|
||||
row = next(
|
||||
(
|
||||
p
|
||||
for p in _visible_providers(cat, config, force_fresh=True)
|
||||
if p.get("name") == body.provider
|
||||
),
|
||||
None,
|
||||
)
|
||||
managed_feature = (row or {}).get("managed_nous_feature")
|
||||
if managed_feature:
|
||||
features = get_nous_subscription_features(config, force_fresh=True)
|
||||
acct = features.account_info
|
||||
category = MANAGED_FEATURE_COVERAGE_CATEGORY.get(managed_feature)
|
||||
entitled = bool(
|
||||
acct
|
||||
and acct.logged_in
|
||||
and (
|
||||
acct.tool_gateway_entitled_for(category)
|
||||
if category
|
||||
else acct.tool_gateway_entitled
|
||||
)
|
||||
)
|
||||
if not entitled:
|
||||
response["needs_nous_auth"] = True
|
||||
response["feature"] = managed_feature
|
||||
return response
|
||||
|
||||
|
||||
class ToolsetEnvUpdate(BaseModel):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue