Snapshot the selected stored session and route token for the full async submit
pipeline so a mid-flight session switch cannot resume the wrong chat or
misroute the user's text. Includes regression tests.
Fixes#54527 — a message typed into one TUI session could be silently
misrouted into (or overwritten by) another concurrently-open session.
Root cause: activeQueueSessionKeyRef is written on every render, but the
debounced draft-persist timer, the pagehide flush, and dispatchSubmit's
reject-restore path all read it lazily at async-resolve time instead of
capturing the scope that was active when the operation started. A session
switch landing between capture and resolve relabels one session's text
under the other session's key. A large paste widens the window (slower
synchronous render), which matches the original report.
Fix: introduce draftScopeRef, written only by the draft-swap effect (so it
always reflects the session whose text is actually loaded in the editor)
and read it instead of the render-time ref at both async write sites.
dispatchSubmit's restore() now uses the submittedScope already captured at
dispatch instead of re-reading the live ref.
Also adds isPendingDraftPersistCurrent as defense-in-depth: before the
debounce timer commits a write, it verifies its captured {scope, text}
pair is still the one on file. This is a no-op under the fix above (a
session swap or a newer keystroke already clears/replaces the pending
entry via clearTimeout), but turns any future regression that reintroduces
a stale/live-ref read at this call site into a dropped write instead of a
silent cross-session misroute.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The SLASH_TRIGGER_RE regex used (?:^|[\s]) as its left anchor, so typing
a / anywhere in the message (e.g. "hello /") opened the slash command
popover — even though slash commands only execute at the beginning of a
message. Anchor the regex strictly at position 0 (^) so the popover only
appears when / is the first character, matching the actual execution
semantics. The @-mention trigger is left untouched since those work
anywhere in the text.
Add a glyph-agnostic ParticleField (float-up + organic sway/bank + springy
pop-in), skinned as pink pixel hearts. Hearts play on the pet when one is out
(in-window or popped out) and celebrate alongside; otherwise they rise from the
composer. A generic $petReaction bus mirrors the burst to the pop-out overlay
window so it reacts even while the app is minimized.
Consume the core `reaction` event to fire hearts on affectionate messages. DEV
Shift+H previews a burst.
Radix's hoverable-content grace area can leave tips stuck over Electron drag regions; disable it and make tip content pointer-events-none so open state tracks the trigger only.
Switching connection mode (local / cloud agent / remote) no longer
full-window-reloads into the cold-boot CONNECTING screen. The primary
backend is torn down in place (no renderer reload); the shell + Settings
stay up while session lists are wiped so sidebar skeletons retrigger, then
the socket re-dials and config/sessions refresh. Cold-boot CONNECTING
latches off after the first successful boot; the intentional teardown
suppresses the backend-exit toast. Dev affordance: a "Preview soft switch"
button under Gateway diagnostics (Electron has no ?query= entry).
Gateway settings UI brought in line with the rest of Settings:
- Mode cards use the shared selectableCardClass on an equal-height
auto-rows-fr grid, stacking 1→3 (never an orphaned 2+1); titles wrap
instead of truncating.
- Remote gateway's auth detail moves into a ? tooltip in the title; drop
the redundant "connects to the one you choose" from the cloud card.
- textStrong buttons force px-0 so the underline sits flush with the label.
- Tooltip chip uses box-decoration-break: clone so the background hugs each
wrapped line (bg only on the text), capped at max-w-64.
Fully i18n'd (en + zh; ja/zh-hant inherit via defineLocale).
Match the sibling pattern (pet-generate/generate-unavailable.tsx): inline the
portal URL literal in the ExternalLink href instead of a one-off named const.
Drop the portalBaseUrl→IPC→useState plumbing I added for the "create an agent"
link. HERMES_PORTAL_BASE_URL is a dev/staging-only override; threading it
through cloud.status() into React state just to build one link isn't worth it —
in prod it's always portal.nousresearch.com. Module-level constant instead.
Per review: the empty-state "create an agent" link went to the generic portal
agents list; point it at the Hermes Cloud create-instance flow
({portal}/cloud?setup=instance) instead. Derive the host from the portalBaseUrl
that cloud.status() already echoes so it honors HERMES_PORTAL_BASE_URL rather
than hardcoding a second copy of the portal host. Link text/copy → "Hermes
Cloud" (en + zh).
Tighten the salvaged Hermes Cloud code with no behavior change:
- main: one `trimCloudOrg` projection reused by the success-echo and the 409
org list (drop the duplicated map), and a `cloudLoginError()` factory for the
three needsCloudLogin throw sites.
- renderer: a `cloudLoginLapsed()` predicate for the duplicated
needsCloudLogin→signed-out check.
Cleanups on top of @ben's Hermes Cloud salvage:
- isConnectedAgent normalized both sides of the cloud-URL comparison (trim +
drop trailing slash + lowercase). The saved URL is host-lowercased by
normalizeRemoteBaseUrl but the discovered dashboardUrl is raw from NAS, so
a host-casing difference could silently break the connected-highlight.
- DesktopCloudStatus.signedIn doc said "AT-or-RT"; it actually reflects the
Nous portal Privy session (privy-token), not the gateway cookies.
Adds a third "Hermes Cloud" gateway mode to the desktop app: one portal
sign-in auto-discovers the agents on your account and connects to any of
them with no second interactive prompt.
- Electron: widen connection mode to 'local' | 'remote' | 'cloud', routed
through a centralized modeIsRemoteLike() so every resolution site treats
cloud exactly like remote; portal discovery (GET /api/agents over the
OAuth partition), Privy-cookie liveness, multi-org picker (NAS 409), and a
silent per-agent /oauth cascade (load protected root, not /login).
- Persist a cloudOrg on the cloud block; unselect cloud on mode switch.
- Renderer: Hermes Cloud ModeCard + agent picker (signed-out/loading/empty/
list), org picker, Change-org, connected-highlight + Connected pill.
- i18n (en + zh full; ja/zh-hant inherit via defineLocale), Cloud icon.
- IPC: hermes☁️{status,login,logout,discover,agent-sign-in}.
Salvage of #55402 onto current main: the original branch predates the
desktop electron .cjs -> .ts migration (39d09453f), so the electron half
was re-authored against the .ts files. Authorship preserved.
cloud-auto-discovery Phases 3 + 4.
The model often emits a follow-up batch of tool calls as its own
assistant message with no prose or reasoning. On screen those rows look
like one continuous run, but assistant-ui only groups tool calls within a
single message, so the auto-scrolling tool window never triggered on them
(e.g. two batches of two searches read as 2 + 2, never reaching the
threshold).
Coalesce each settled tool-only assistant message into the preceding
assistant message in the render pipeline so its calls join that message's
tool group. Render-only (never touches the $messages store) and
settle-only (pending messages are skipped) so a live turn is never
merged/un-merged mid-stream; merged results are cached by source identity
so a stable turn yields stable objects with no re-render churn.
Two client-side halves of the #55578 session split:
1. Submit with a null activeSessionId but a SELECTED stored session now
resumes that stored session instead of falling straight through to
createBackendSessionForSend - which silently forked the user's
conversation into a brand-new session that then got orphan-reaped.
New-chat drafts (no stored selection) still create sessions as before.
2. prompt.submit recovery now also fires on gateway request timeouts,
not only 'session not found'. A starved backend loop (the async-
delegation poller spin) rejects the submit with 'request timed out'
even though the stored session is fine; previously that surfaced an
error, left the binding cleared, and set up the split on the next
send.
Fail-then-pass: 2 new tests fail with production code reverted.
The desktop app's chat panel reuses tui_gateway as its backend, so every chat session was stamped platform="tui". That made the agent read terminal-specific platform guidance while running in the graphical desktop chat surface.
Resolve the misclassification at its source: tui_gateway now picks platform="desktop" when HERMES_DESKTOP=1 and HERMES_DESKTOP_TERMINAL is unset, and keeps platform="tui" for the embedded terminal pane and standalone TUI. Add a PLATFORM_HINTS["desktop"] entry describing the actual chat surface (full GFM markdown, MEDIA: intercept, inline images). Move the embedded-pane clarifier to the platform-hint resolution site so it appends only to the tui hint under HERMES_DESKTOP_TERMINAL=1. Delete the now-dead desktop-hint block from build_environment_hints() that competed with the platform hint.
Standalone TUI sessions produce byte-identical prompts as before; the new desktop hint and clarifier are assembled once per session in the stable tier, so prompt caching is preserved.
* feat(install): warn pip/Homebrew installs are unsupported (CLI, TUI, desktop)
pip and Homebrew are now Unsupported install methods per
website/docs/getting-started/platform-support.md. Surface a
warn-don't-block deprecation notice everywhere the install method is
already shown, pointing at the platform-support docs and noting these
installs will not receive further updates. NixOS (Tier 2) is untouched.
- hermes_cli/config.py: shared is_unsupported_install_method() /
format_unsupported_install_warning() helpers so the wording and docs
link stay consistent across every surface.
- hermes_cli/banner.py: generalize the existing pip-only banner
warning to also cover Homebrew.
- hermes_cli/main.py: hermes update and hermes update --check print
the warning before proceeding (still update; warn, don't block).
- tui_gateway/server.py: session.info gains install_warning.
- ui-tui: SessionPanel renders install_warning alongside the existing
'N commits behind' notice.
- apps/desktop: SessionRuntimeInfo/GatewayEventPayload gain
install_warning; applyRuntimeInfo + the live session.info event fire
a snoozable warning toast via a new reportInstallMethodWarning(),
mirroring the existing backend-contract-skew toast pattern. i18n
strings added for en/zh/zh-hant/ja.
- Tests: updated pip banner assertions for the new wording, added a
Homebrew banner test, and two tui_gateway session_info tests
(install_warning present for pip, absent for git).
* fix(nix): make `hermes` in developement environment actually work
install modules as editable overlay with uv
* feat: print install method when running --version
* fix: correct detect install method when running from a subtree
Drop the duplicate mermaid-block.tsx (own mermaid.initialize + render path,
theme frozen at first load) and wire preview-file.tsx's MarkdownCode through
the existing RichCodeBlock registry from #52935 instead. One mermaid init
path, theme-flip re-init, Zoomable + copy-as-PNG, RichBoundary error
fallback — and the preview pane gets svg fences for free. Shiki block stays
as the fallback for all other languages.
Salvaged from #40531; surgically reapplied onto current main (i18n'd
preview-file.tsx). mermaid dep already present on main.
Co-authored-by: liuhao1024 <liuhao1024@users.noreply.github.com>
The Capabilities/MCP/Hub/Skills UX has settled, so lift every
`// TODO(i18n): literal until the UX settles` hardcoded English string into
the typed i18n catalog and drop the comments.
- New keys under `common` (expand, tryHint), `settings.mcp` (capability
summary, status line, all-servers, auth flow, tool chip titles, log empty
label), and `skills` (provenance, sort/bulk labels, empty states, editor
actions). Full translations in en + zh; ja + zh-hant overrides added.
- Module-level pure fns that had no `t` in scope now take the mcp translations
(`capabilitySummary`/`statusLine`) or an `emptyLabel` prop (`McpLogs`); the
archive toast takes `t`.
- Shared `common.tryHint(term)` dedupes the "Try “…”" search hint across
skills/messaging/cron/artifacts.
No behavior or styling change — string lookups only. Zero TODO(i18n) remain.
Post-merge follow-ups + several review rounds + a hub-search rework, folded together.
Merge-scuff restores (a stale-base refactor had reverted two live-on-main fixes):
- gateway: SessionStore compression-tip healing + its regression test.
- desktop: messaging session/transcript polling in desktop-controller
(MESSAGING_POLL / ACTIVE_MESSAGING_SESSION_POLL, refreshMessagingSessions,
refreshActiveMessagingTranscript, the richer sameCronSignature) so inbound
platform traffic updates live again instead of freezing until manual refresh.
Profile-switch isolation (epoch/close/guard on every profile-scoped async):
- Hub store clears + in-flight runHubAction bails (and swallows the post-switch
404 instead of a phantom toast); hub preview/scan/search/sources profile-scoped.
- MCP: probe/auth epoch guards, dirty-draft reset, sidebar mutations blocked
until config resettles AND every persist re-checks the epoch post-await;
profilePending clears on config settle incl. error; logs re-key on profile.
- Model settings reload on switch and epoch-guard setModelAssignment /
saveMoaModels / API-key activation.
- Config draft resets + cancels its autosave on switch; skill editor/archive and
star-map node dialogs close on switch; openSkillEditor / star-map openEdit
discard stale fetches; tool-usage analytics loads are profile-guarded/keyed.
Correctness + UX:
- Unique per-skill action names for hub install AND uninstall; hub/catalog rows
flip only on a clean exit_code; catalog install polls the background bootstrap
to completion, reconciles the mcp.json draft (no dropped server), and fails
loudly on non-zero exit; MCP catalog query keyed by profile.
- /test reports needs-auth for anonymous auth:oauth servers; /auth snapshots +
restores tokens on a failed re-auth and clears the full 300s callback window.
- config-settings shows a retry on load failure; CodeEditor/JsonDocumentEditor
go read-only while saving so edits typed mid-save aren't dropped.
- Deep-link highlighter deletes its param only after a successful scroll.
- Restored the PageSearchShell trailing slot → Artifacts refresh button/spinner.
- /settings?tab=mcp redirect keeps server=.
Progressive hub search: fan out one query per backend-searchable source
(index-covered API sources stay unsearchable → no ~70-call GitHub re-hammer),
merge/dedupe by trust as each lands, per-source spinner overlaid on the dimmed
chip — results stream in without blocking on the slowest, no layout shift.
test(web): /api/skills list carries usage + provenance (CI contract).
A back-to-back run of 3+ adjacent tool calls now collapses into a
fixed-height window that pins the newest call to the bottom and fades
older ones up under a top gradient, so a long run no longer shoves the
reply off screen. Shorter runs are byte-identical to before, and the DOM
shape is the same in both modes (only classes flip) so crossing the
threshold mid-stream never remounts a row. Expanding any row breaks the
window out to full height via a `:has([data-tool-open])` rule.
Route the app off its hand-rolled helpers onto lib/{text,time,format,json-format}
and the new primitives, plus assorted small tidy-ups:
- compactNumber for counts/tokens; normalize/capitalize/asText at the many
filter/label sites; shared Intl date/time formatters; row-hover + framed
editor adoption; scrollbar-gutter + padding parity on list surfaces.
- Messaging/Artifacts/Cron search hints + narrow-viewport tab dropdown;
floating-pet adopts useOnProfileSwitch; number formatting in statusbar,
command-center, agents.
- Electron: native overlay width + backend spawn tidy.
- Settings > Keys: credential fields read as plain subtext (all-unset) until
the group is focused or expanded, then take full input chrome with no
horizontal/vertical shift; inline Remove (trash) + Save mirror SearchField's
trailing-clear pattern instead of a floating hint that overlapped the card;
Esc still cancels. Drops the now-dead or/escToCancel i18n keys.
- Shared TabDropdown/ResponsiveTabs (components/ui): PageSearchShell and the
Command Center log file/level filters reuse the one narrow-width collapse.
- OverlayNav: data-driven pane nav — persistent rail on wide, a single dropdown
riding the titlebar strip on narrow; Settings and Command Center adopt it, and
the mobile dropdown carries the same section icons as the rail. Fixes narrow
vertical centering, redundant mobile section titles, gateway-status wrap, and
Panel master/detail stacking.
- OverlayIconButton is now the titlebar ghost button, matching the close X at
every size. Settings sub-view nav opens section + sub-view in one navigate so
API-keys/accounts actually open on narrow.
- Settings > Model: cube icon (was the {} namespace glyph) and a DOM-shaped
skeleton in place of the centered spinner.
- Command palette / session switcher clear the macOS traffic lights on small
screens.
- Prettier/eslint sweep across the touched files.
Fold the skill hub (search/preview/scan/install, from #57441) into Capabilities
as a fourth "Browse Hub" tab, rebuilt on our stack:
- sources + debounced term search + preview are useQuery-driven (RQ dedupes/
caches per term, cancels stale terms — no hand-rolled sequence guard).
- Each result is a self-contained HubSkillRow that installs/uninstalls ITSELF,
reading its own status from a nanostore (store/hub-actions). Concurrent
installs never desync; an optimistic installed-override flips a row the instant
its own action resolves instead of racing the sources refetch.
- Action log bubbles through a $hubActiveLog atom into the shared LogTail in a
collapsed-by-default, persistent bottom DetailPane (ANSI stripped).
A Cursor-style MCP manager inside Capabilities, plus the backend it needs.
- Server list with brand/favicon avatars + live status dot and a capability
summary (N tools, M prompts, K resources); Servers | Catalog views.
- Catalog: one-click install of Nous-approved servers with required-env prompts.
- GUI OAuth: Authenticate opens the system browser from the TTY-less backend and
verifies a token actually lands; header/API-key servers are never pushed down
OAuth; a dirty mcp.json can't drop a freshly-persisted auth field.
- Full-width mcp.json editor (ecosystem document format) + pinned stdio/agent
LogTail; probes cached 5m and keyed by (profile, config) so revisiting never
respawns the fleet or shows a stale probe.
- Whole-map persistence (PUT /api/mcp/servers) so deletes/toggles actually stick
(the generic /api/config deep-merge could not remove keys).
- perf: MCP probe/auth no longer hold the global skills lock, so a slow stdio
spawn can't stall every other request into a 15s timeout.
- per-tool include/exclude gating (lib/mcp-tool-filter) mirroring the CLI loader.
Merge the old Skills + Toolsets tabs and pull MCP out of Settings into one
master-detail "Capabilities" hub (Skills / Tools / MCP / Browse Hub).
- Skills: usage-sorted from real per-skill activity, provenance badges
(learned / built-in / hub), edit + archive for learned skills, per-tab bulk
toggle; full-bleed empty states.
- Tools: usage-aware, container-queried rows (no early two-column collapse).
- Settings panels move onto the shared config-record query cache; the deleted
Settings MCP page redirects (/settings?tab=mcp → /skills?tab=mcp).
- Lazy, profile-scoped, TTL-cached usage analytics so the heavy 365-day scan
never blocks the Skills/MCP tabs. Full en/zh strings (ja/zh-hant inherit).
The active-transcript poll armed a 5 s timer for every selected session
and no-op'd inside the tick for local chats (already live over the
websocket). Derive activeIsMessaging and gate the effect on it so local
chats never spin an idle timer.
Inbound Telegram/WeChat/Discord messages are written by the background
gateway, not the desktop websocket that drives local chats. Without
explicit polling the messaging sidebar and the open transcript stay
frozen until the user manually refreshes.
Desktop:
- MESSAGING_POLL_INTERVAL_MS (10 s): interval poll of the messaging
session list so new platform sessions surface automatically.
- ACTIVE_MESSAGING_SESSION_POLL_INTERVAL_MS (5 s): poll the currently-
viewed messaging transcript and re-hydrate the chat state when the
FNV-1a signature changes (hash covers role + timestamp + content).
- sameCronSignature now compares lineage_root_id / source / profile /
preview / message_count / last_active / ended_at so stale previews
and activity times are no longer silently ignored.
- sessionMatchesStoredId helper de-dups the id / _lineage_root_id check.
- refreshMessagingSessions exposed from useSessionListActions so the
controller can use it in the poll effect.
Gateway:
- SessionStore._compression_tip_for_session_id: look up the latest
compression continuation for a session id.
- SessionStore._heal_compression_tip_locked: rewrite a stale entry to
the compression child before returning it, so a restart or failed send
no longer leaves the store pinned to the compressed parent.
Co-authored-by: lawyer112 <lawyer112@users.noreply.github.com>
* feat(desktop): CLI/dashboard parity — skills hub browser, MCP test/toggle/catalog, maintenance ops, log filters
Brings desktop GUI to parity with hermes skills/mcp/doctor/backup/debug-share/
curator/memory CLI commands and the dashboard's System + Skills-hub pages:
- Skills page: new Browse Hub tab (search official/GitHub/community sources,
preview SKILL.md, security scan verdicts, install/update with live action log)
- MCP settings: connection test (tool listing), per-server enable/disable
toggle, and a Catalog tab installing Nous-approved MCP servers with env prompts
- Command Center: new Maintenance section (doctor, security audit, backup,
debug share links, curator status/pause/run, memory file status + reset)
- Command Center system logs: file (agent/errors/gateway/desktop), level, and
substring filters instead of a fixed agent.log tail
- hermes.ts API client + types for all the above; en/zh locale strings (ja and
zh-hant inherit via defineLocale)
* feat(desktop): backend model catalogs in toolset config — hermes tools parity
Completes the `hermes tools` parity gap: after picking an image/video
generation backend the CLI runs a model picker (e.g. FAL's multi-model
catalog with speed/strengths/price); the desktop toolset drawer now has the
same flow as a radio-card list.
- web_server: GET /api/tools/toolsets/{name}/models (catalog + current +
default for the active or named provider row) and PUT .../model
(validated write to image_gen.model / video_gen.model), reusing the CLI's
plugin catalog helpers so GUI and `hermes tools` stay in lockstep
- desktop: ModelCatalogPicker in ToolsetConfigPanel — per-model cards with
speed/strengths/price, in-use + default badges, disabled until the
backend is the active one; provider selection now mirrors is_active
locally so the catalog unlocks without a refetch
- tests: 3 backend endpoint tests (catalog shape invariants, persist +
validation), 2 component tests, 2 API-contract tests; en/zh strings
A turn that ends without a final `todo` update left the composer "Tasks N/M"
panel pinned with its last item stuck pending/in_progress, and it survived
restarts because the panel is read back from stored session history.
Two coupled fixes (the first alone is undone by the second path):
- Turn end: clear a still-active todo list on `message.complete` and on a
terminal `error` (new `clearActiveSessionTodos` — active lists only; a
finished list keeps its short linger so the last checkmark still lands).
- Rehydration: `hydrateFromStoredSession` runs *after* a turn completes, so an
"active" stored list is stale, not in-flight. It now restores only a
*finished* list (via new `todosForHydration`) and drops anything still
active — otherwise it re-pinned the panel right after the turn-end clear and
resurrected it on every restart.
Salvages #52996 (@0disoft): the fix shape (clearActiveSessionTodos on turn
completion, preserving the finished-list linger) is carried forward and ported
onto the current use-message-stream/ folder split (gateway-event.ts), then
extended to the rehydration path per review.
Co-authored-by: 0disoft <rodisoft1@gmail.com>
Add a shared PAGE_MAX_W (1200px) and center OverlayMain within its pane
so settings and command center bodies stay readable instead of sprawling
on wide/ultrawide displays.
Broadens Tranquil-Flow's profile-startup timeout fix (#48518) from getProfiles
+ refreshActiveProfile to the rest of the calls the desktop fires during
connect: /api/config, /api/config/defaults, /api/model/info, /api/model/options,
/api/cron/jobs. On a profile-heavy or remote install any of these can exceed
the 15s DEFAULT_FETCH_TIMEOUT_MS while the backend is alive-but-busy (e.g.
list_profiles walks the skill tree per profile), surfacing as the spurious
"Timed out connecting to Hermes backend after 15000ms" that hangs the UI
(#48504).
Uses the surgical per-call mechanism (renamed STARTUP_PROFILE_REQUEST_TIMEOUT_MS
→ STARTUP_REQUEST_TIMEOUT_MS) rather than raising the global default (the
alternative in #48526): the liveness poll /api/status and all interactive/
runtime calls keep the short default, so a genuinely-dead backend is still
detected fast and the boot readiness probe (waitForHermes) is untouched.
Supersedes #48518 (carried as the base commit) and #48526 (global-default
raise). Fixes#48504.
Co-authored-by: YapBi <129007007+HeLLGURD@users.noreply.github.com>
Co-authored-by: Tranquil-Flow <66773372+Tranquil-Flow@users.noreply.github.com>
A Z.ai desktop user reported thinking reverting to medium after one turn,
burning ~200% of a week's credits in 4 days despite reasoning_effort: false
in config.yaml. Four compounding bugs:
- _session_info reported reasoning_effort "" for disabled reasoning,
indistinguishable from unset — the desktop adopted it after the first
turn, wiping its sticky "thinking off" pick so every later chat
reverted to the default effort.
- config.set key=reasoning always wrote agent.reasoning_effort to global
config.yaml, so every desktop model-menu selection (preset.effort ??
'medium') clobbered the user's configured value. Now session-scoped
like the messaging gateway's /reasoning, landing on
create_reasoning_override so lazily-built sessions keep it too.
- YAML `reasoning_effort: false`/`off`/`no` (boolean False) was coerced
to "" by every loader's `str(x or "")`, silently re-enabling thinking.
parse_reasoning_effort now treats False/"false"/"disabled" as
{"enabled": False}; loaders (tui gateway, gateway, cli, cron,
delegate) pass the raw value through. The desktop config reader also
crashed on the boolean (false.trim()), aborting voice/STT settings.
- The zai provider profile never sent thinking on the wire, and GLM-4.5+
defaults to thinking ON server-side — so disabling reasoning was a
silent no-op on direct Z.ai, the actual token burner. The profile now
emits extra_body.thinking {"type": "enabled"|"disabled"} for
thinking-capable GLM models, mirroring the DeepSeek profile.
Also: /new (session reset) now carries reasoning_config across the
rebuild like model_override; config.get reasoning prefers the session's
live value and maps a config False to "none"; Settings shows "Off"
instead of a blank select for hand-written false.
Replace the loopback/PKCE-callback server and manual-paste fallback with
the RFC 8628 device-code flow as the only xAI Grok OAuth login path. The
flow works in headless/SSH/container sessions with no 127.0.0.1 listener,
shrinking the local attack surface.
- Poll the token endpoint with server-provided interval, honoring
slow_down and expires_in; store tokens with auth_mode
oauth_device_code.
- Adaptive proactive refresh skew for short-lived device-code JWTs;
rotated tokens sync back to auth.json, the global root store, and the
credential pool (no refresh-token replay).
- Clear source suppression on successful re-login (CLI + dashboard) and
drop the duplicate dashboard pool entry so exactly one seeded
device_code entry exists.
- Use the shared device_code source name for consistency with the
nous/codex device-code providers.
- Desktop: remove the loopback OAuth flow states and dead type variants;
pkce providers' sign-in URL selection is unchanged.
- Docs (EN + zh-Hans) rewritten for device-code login; drop the deleted
--manual-paste flag from documented commands.