Commit Graph

21297 Commits

Author SHA1 Message Date
bex 9f582aca1d fix(agent): read Telegram rich_messages config from correct path
Commit b45a217e0 gated the TELEGRAM_RICH_MESSAGES_HINT extension behind
a config read at the top-level ``platforms.telegram.extra.rich_messages``
key, but the Telegram adapter reads the same setting from the canonical
``gateway.platforms.telegram.extra.rich_messages`` path.  When users set
the setting in the canonical location (the only one documented), the
lookup returned None and the extension never fired — the model degraded
pipe tables to bullet lists, task lists to plain dashes, and never
produced <details> blocks or block math.

Fix: merge both ``gateway.platforms.telegram.extra`` and the top-level
``platforms.telegram.extra`` with the same precedence the adapter uses
(top-level leaf wins), so config-wizard writes and dashboard-setup keys
are visible alongside the canonical gateway location.  Narrow the
except-guard to ImportError so real config-stack failures surface.
2026-08-08 13:57:44 +05:30
kshitij 0041fc6946 refactor(simplex): hoist json.dumps above branch in _standalone_send
Deduplicate the composed JSON payload construction — both the group
and DM branches build the identical json.dumps result, so hoist it
above the if/else to match the pattern already used in send().
2026-08-08 13:56:12 +05:30
liuhao1024 eb048772f6 fix(simplex): use structured /_send for standalone DM text sends
The _standalone_send() function (used by the send_message tool for
proactive/scheduled sends) has the same bare `@<id> text` bug that
send() had before PR #44444. SimpleX's `@<x>` syntax resolves x as a
display name, not a contactId — the daemon silently drops messages
when it cannot find a contact named "6".

Use the structured `/_send @<id> json [...]` form, matching what
send_image, send_document, and the send() fix already use.

Fixes #46265
2026-08-08 13:56:12 +05:30
liuhao1024 3a04d9c4d7 fix(simplex): use structured /_send for DM text messages to prevent silent drops 2026-08-08 13:56:12 +05:30
kshitij 077e6170a9 test(gateway): cover same-PID differing non-null start_time self-reacquire
Adds regression test for the case where both disk and live start_time
are known integers but differ (e.g. stale value from a previous run).
The self-PID short-circuit must fire regardless — start_time only
guards PID reuse for *other* PIDs. Inspired by #81495's test case.
2026-08-08 13:50:08 +05:30
HexLab98 e54ba2ade2 test(gateway): cover null start_time scoped-lock self-reacquire
Regression for Discord 503 reconnect false-positive discord-bot-token
lock against the live gateway PID (#81468).
2026-08-08 13:50:08 +05:30
HexLab98 2e18e29723 fix(gateway): self-reacquire scoped lock by PID alone
After Discord reconnect, on-disk start_time can be null while the live
record has a fingerprint. Requiring equality made the gateway treat its
own PID as a foreign token holder (#81468).
2026-08-08 13:50:08 +05:30
Gille 5077665b88 test(wake-word): verify resampled audio values 2026-08-08 13:49:24 +05:30
Gille e3be3b0481 fix(wake-word): capture at native input rate
Open the selected microphone at its reported default rate and convert each capture block to the 16 kHz frame expected by wake-word engines. Add a regression covering a 48 kHz WASAPI device.

Co-authored-by: clyu168 <clyu168@126.com>
2026-08-08 13:49:24 +05:30
kshitij 2ddd24ec1f fix: use is_job_runnable/effective_job_state in remaining pause-check sites
Two claim-failure diagnostic paths (cronjob_tools.py:629,921) still used
the old inline 'not enabled or state==paused' check. After get_job()
normalizes via effective_job_state, a half-paused record has
state='scheduled' and enabled=True, so the inline check returned False —
mislabeling the job as 'already being fired' instead of 'paused/disabled'.

Also hoists effective_job_state/is_job_runnable to the top-level import in
cronjob_tools.py (was function-local) and updates console_engine.py's
_format_job to use effective_job_state instead of the old inline
state-or-enabled derivation — a fourth display path the original PR missed.

Follow-up to PR #81287.
2026-08-08 13:48:00 +05:30
rjvandeve c7a5de7d6e fix(cron): make pause authoritative against half-paused records
pause_job already sets enabled=false atomically with state/paused_at, but
get_due_jobs only checked enabled — so a contradictory record
(enabled=true + paused_at/state=paused) still fired. That was the 07-30
outage failure mode: list looked frozen, fleet kept merging.

- is_job_runnable / effective_job_state: pause markers gate fire; display
  derives from the scheduler-honoured enabled flag so half-paused never
  renders as [paused]
- get_due_jobs self-heals enabled=false + logs error on contradiction
- claim_job_for_fire uses is_job_runnable (paused_at counts too)
- list/format paths use effective_job_state
- behavioural tests: pause blocks due fire; half-pause self-disables
2026-08-08 13:48:00 +05:30
hermes-seaeye[bot] 2d5e93161b fmt(js): npm run fix on merge (#81589)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-08 08:15:17 +00:00
kshitij a8ccd52123 refactor(sessions): accurate scope wording for tip-only resume rejections
SessionResumeTooLargeError said 'across its lineage' even when the CLI
mid-setup path counted only the tip segment; the exception now takes a
scope phrase.
2026-08-08 13:36:08 +05:30
kshitij edf2cb4bf8 perf(sessions): skip counting entirely when transcript guards are disabled
With sessions.max_*_messages: 0 the guards previously still ran an
unbounded COUNT (full lineage for resume) — the exact pathological work
disabling them is meant to avoid. Live callers use the raise side
effect only, so return 0 without touching the messages table.
2026-08-08 13:36:08 +05:30
kshitij ad59bd92c7 test(desktop): align renamed message-fetch mocks; brace query-param guards
The salvage's getLatestSessionMessages/getAllSessionMessages split left
two desktop test files mocking the old getSessionMessages name (vi.mock
partial-mock let the real function through, so calls hit the un-mocked
path). Also braces the new single-line if guards per the curly lint
rule.
2026-08-08 13:36:08 +05:30
kshitij e8b05dc6c2 perf(dashboard): keyset pagination for streaming session export
OFFSET paging made the streaming export O(n^2) on huge transcripts;
after_id keyset paging keeps each page seek O(1). Adds after_id to
SessionDB.get_messages (ascending-only, guarded against latest/offset
combos).
2026-08-08 13:36:08 +05:30
kshitij 5b4b9bbf77 fix(sessions): tip-only resume guard on the CLI mid-setup path; fail open on guard errors
The mid-setup CLI resume path loads only the tip session's rows, so
gate it with a tip-only count instead of the full-lineage count (which
over-rejected heavily-compressed sessions). Transient guard failures
(locked DB, adaptor stores) now log and proceed instead of blocking
resume with a new error.
2026-08-08 13:36:08 +05:30
kshitij 2607dc9a85 fix(desktop): forward pagination params through the remote session interceptor
The remote interceptor rebuilt session/messages requests from pathname
only, silently dropping limit/offset/order. Against a paginating remote
backend, getAllSessionMessages would refetch the same default page until
the safe-load guard threw, breaking export/artifacts/branch for remote
sessions over one page.
2026-08-08 13:36:08 +05:30
kshitij f0794640f6 feat(sessions): config-gate transcript safety limits
sessions.max_resume_messages / sessions.max_export_messages (default
20000, 0 disables) replace the hardcoded hard-rejects, and the CLI
'sessions export' guard becomes per-session instead of cumulative so
full-DB backups of many small sessions keep working. Error guidance now
points at the config override instead of the (corruption-only) repair
command.
2026-08-08 13:36:08 +05:30
kinsolee c750d5354a fix(sessions): prevent oversized transcripts from exhausting memory 2026-08-08 13:36:08 +05:30
kshitij 643910afe3 refactor(gateway): narrow worker-start guard to Exception
Thread.start() failure is RuntimeError; catching BaseException here
swallowed KeyboardInterrupt/SystemExit without re-raise (unlike _worker,
which forwards them into the future).
2026-08-08 13:31:04 +05:30
kshitij 0d312126a0 test(gateway): de-flake history-lookup timing tests; add worker-start-failure regression 2026-08-08 13:31:04 +05:30
kshitij 38cd1999cb fix(gateway): fail open + release admission slot when history-lookup worker cannot start 2026-08-08 13:31:04 +05:30
HenryG 271867f6fa fix(gateway): bound media history workers 2026-08-08 13:31:04 +05:30
HenryG e52acf76a1 fix(gateway): keep media history reads off event loop 2026-08-08 13:31:04 +05:30
kshitij c360333a3f test(dashboard): deterministic lock gating + plugin-providers RMW regression
- Heartbeat tests: holder signals a threading.Event after acquiring
  _SKILLS_PROFILE_LOCK; the scenario waits on it via run_in_executor
  instead of sleeping 50ms and hoping.
- Fix the TestConfigMutationLock comment to describe the probabilistic
  slow-save interleave the code actually implements.
- New regression test: PUT /api/dashboard/plugin-providers must hold
  _CONFIG_MUTATION_LOCK — a concurrent locked writer survives.
2026-08-08 13:28:28 +05:30
kshitij 4ecdee38a6 fix(dashboard): close config-RMW gaps left by the off-loop sweep 2026-08-08 13:28:28 +05:30
Royalaid 965a548788 fix(gateway): serialize config mutations and finish the router off-loop sweep
Two follow-ups to the off-loop move, from external review (both verified,
the second larger than reported):

- Config read-modify-write handlers moved to worker threads could now
  interleave — _CONFIG_LOCK covers each load/save individually, never the
  span between them; the event loop used to serialize these accidentally.
  New _CONFIG_MUTATION_LOCK (worker-threads only, so it can never block
  the loop) held across the whole load→mutate→save span in all seven RMW
  handlers. update_config_raw skipped: it's a full-document replace with
  no server-side read, so a lock cannot close its client-side window.

- The review flagged two skills routes still taking _SKILLS_PROFILE_LOCK
  on the event loop; a systematic audit of hermes_cli/web_routers/ found
  24 on-loop routes (skills 5, mcp 9, tools 10, cron 1). All moved to the
  same inner-_run + asyncio.to_thread pattern, mutating ones under the
  mutation lock, uniform lock order (_SKILLS_PROFILE_LOCK →
  _CONFIG_MUTATION_LOCK). Await-safe _config_profile_scope routes, plain
  def routes, and already-threaded routes unchanged.

Regression tests: concurrent theme+font updates both survive (fails with
the lock nulled: "theme write lost to a concurrent font write"); event
loop stays responsive while the profile lock is held during GET
/api/skills. 214 tests passing across the touched suites.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-08 13:28:28 +05:30
Royalaid 52c9aee3bf fix(gateway): move _profile_scope and config I/O off the event loop in async handlers
The diagnostics loop watchdog caught GET /api/config freezing the gateway
event loop for >1s, stack-sampled blocking on _SKILLS_PROFILE_LOCK inside
_profile_scope. Any async handler that entered _profile_scope (process-wide
threading lock) or called load_config()/save_config() on-loop could stall
every chat and WebSocket at once while a slow lock-holder ran.

Move 28 such handlers to the existing inner-_run + asyncio.to_thread
pattern (contextvar-safe: the whole scope enter/body/exit stays inside one
worker thread). Handlers using the await-safe _config_profile_scope, plain
def endpoints (FastAPI threadpool), and tui_gateway's contextvar-only
decorator are unaffected and unchanged.

Regression test holds _SKILLS_PROFILE_LOCK in a thread while calling
GET /api/config and asserts an event-loop heartbeat keeps ticking; it fails
against the pre-fix code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-08 13:28:28 +05:30
gnanirahulnutakki e6b168855b fix(gateway): keep auto vision preprocess concise
Replace the 'describe everything in thorough detail' auto image-preprocess
prompt with a concise 2-4 sentence summary prompt so image-bearing gateway
messages stop generating ~2000-char descriptions (35s+ on local models).

Prompt-only variant of #10852: the max_tokens=500 cap and the
preserve_max_tokens aux-client plumbing from the original PR are
intentionally dropped to stay compatible with the max-tokens-knob policy
direction (#75253 removes hardcoded vision caps).

Fixes #10809
2026-08-08 13:28:19 +05:30
Brooklyn Nicholson a3d57f18c2 fix(desktop): open HUD mode on the tab you're looking at
Both entry points read $selectedStoredSessionId, which is the WORKSPACE pane's
session — so whichever tile was fronted, the main tab went into the HUD. Tabs
exist precisely so those aren't the same question.

`getActiveComposer()` already answers it for the focus bus, healing to the
visible surface when its cached claim is buried, and a tile's routing key is its
stored session id. One resolver now, shared by the titlebar button and ⌘⇧H.

Coming back re-resumes through the tile delegate when the target is an open
tile. The ordinary resume path enforces "a session is either main or a tile,
never both" and would have closed the tile to take it into main, quietly
rearranging tabs the user opened on purpose.
2026-08-08 02:28:58 -05:00
brooklyn! d92bfa0a38 Merge pull request #81570 from NousResearch/bb/figma-mcp-401
Recover an MCP server that 401s at startup instead of needing a restart
2026-08-08 02:24:59 -05:00
Brooklyn Nicholson f99d291247 fix(mcp): let a server that 401s at startup come back after re-login
An auth failure on the very first connect returned out of the run loop
instead of parking. That ended the run task, and the task is the only
listener on _reconnect_event — so the server stayed dead for the life of
the process. `hermes mcp login`, a /mcp refresh, and the 300s self-probe
all had nothing left to wake, and the only cure was a full restart.

_classify_mcp_failure already calls 401/403 "permanent" and documents
that run() parks those immediately; the early return above it meant auth
was the one permanent failure that never got there. Park it with the
others and keep the tailored log line, now pointing at `hermes mcp
login <server>`.
2026-08-08 02:21:36 -05:00
Brooklyn Nicholson 0e260b3c8c fix(desktop): grow the HUD band smoothly, and flip on the visible panel
The sheet is sized to the transcript, so it changed height on every reply with
no transition — the panel twitched to each new size instead of growing into it.
Animated on the reveal timing.

The edge flip was reading the window, which stopped being the same thing as the
HUD once the sheet started sizing itself: a tall window's top edge reaches the
screen top while the visible bar is still well down the display, so it flipped
far too early. It now measures the visible panel, against a threshold
proportional to the display rather than an exact-flush rule — the HUD hugs its
bar, so its visible top can never actually touch the screen edge and a
zero-tolerance test would never fire at all.
2026-08-08 02:09:58 -05:00
Brooklyn Nicholson ba4456ab00 fix(desktop): don't flip the HUD's fade when the bar parks at the top
Parked at the top the whole gradient flipped with the layout, which inverted the
thing it exists to do. The transcript reads oldest-to-newest downward wherever
the bar is, so the newest turn is at the bottom either way — flipping the mask
faded the newest reply and left the oldest solid, leaving a stray bubble hanging
over the desktop with the reply dissolved beneath it.

Only the anchor flips now: the sheet hangs from the bar instead of standing on
it, the thread's padding moves, and the exit chip drops clear. The fade always
runs away from the newest message.
2026-08-08 02:01:32 -05:00
Brooklyn Nicholson 6ada733f9d fix(desktop): HUD sizing — empty band, stranded exit chip, early stacking
Four things the band's content-sizing exposed, all of them stale assumptions
from when it was a full-window box.

A leftover `:focus-within` rule still painted the band's own background, so
focusing an empty session filled the whole window with a solid slab even though
the sheet had correctly collapsed to nothing.

The exit chip anchored to the band rather than the sheet, so on a short
transcript it floated in empty space at the window's corner. It now rides the
sheet, and drops clear of the bar when the layout is flipped.

Bottom clearance read `--composer-measured-height`, a surface var that never
reaches here, so it silently fell back to the root estimate and reserved ~20px
more than the bar occupies. The bar's real height is measured alongside the
band's.

Top-edge mode was never re-checked after the sheet started sizing itself: the
sheet stayed pinned to the bottom while the transcript hung from the top. Both
the anchor and the measurement now flip with the layout.
2026-08-08 01:59:25 -05:00
Brooklyn Nicholson d57927f3bb fix(desktop): stop the composer's two collapse stages landing together
The model pill shed its label at 440px and the row stacked at almost the same
width, so the chevron bought nothing — which is the one thing a progressive
collapse is meant to avoid.

Sized off what the controls actually cost rather than a guess. With the full
pill they take ~284px, so at 440 the inline input was ~156px against a 128px
minimum: a few words wrapped, and wrapping is what stacks the row. Compacting
at 560 sheds the label while the input still has ~276px, and spends the ~110px
the chevron frees on keeping the row single for another stretch.

Global, not HUD-only.
2026-08-08 01:45:02 -05:00
Brooklyn Nicholson f9860b0508 fix(desktop): size the HUD band to its transcript
An empty session painted a full-height slab of glass with nothing in it. The
sheet now grows up from the bar to fit the transcript and caps at the window, so
a fresh chat is just the bar.

Measured from the topmost row down to the bar and written straight to the
element — it changes on every stream flush, and the sheet resizing must not
re-render the tree.
2026-08-08 01:42:49 -05:00
Brooklyn Nicholson 6a01b429d7 feat(desktop): reach HUD mode from the titlebar and a keybind
⌘⇧H plus a titlebar tool, since the whole point is leaving the app without
reaching for it. Keeps the keyboard-shortcuts button it sat next to.
2026-08-08 01:39:06 -05:00
Brooklyn Nicholson e8b83f37c8 feat(desktop): the HUD surface — Spotlight bar with a fading chat band
The renderer half. HUD mode reuses the app's own chat surface and only changes
the frame around it: no titlebar, no statusbar, no pane tree. The band and the
bar tile the window between them with no dead margins, and the band runs the
window's full height with the opaque bar sitting on its bottom edge, so there
is no seam to compute and none to drift as the composer grows.

Visibility is a WoW chat frame: the transcript shows while a turn is recent or
the composer has focus, then holds and fades. A bottom-anchored gradient mask
carries the fade on both the sheet and the text, and flattens away on focus so
nothing is dimmed while you are reading. Only the composer never fades — it is
the interface.

The window is mouse-transparent everywhere it isn't really there, so clicks over
the faded band reach the app behind it; `pointer-events` can't do this, since
the click never reaches the page at all.
2026-08-08 01:39:06 -05:00
Brooklyn Nicholson 7b0dbd2242 feat(desktop): HUD mode window and its session handoff
A transparent, frameless, always-on-top window that renders the real chat
surface, so its composer is the app's composer rather than a lookalike that
drifts. Main owns the window, its remembered geometry, and click-through.

Leaving is a handoff, not a window close. The gateway binds a session's event
stream to exactly one socket, so a turn started in the HUD streams only there
and the app window hears nothing — no deltas, no turn-complete, no draft clear,
and nothing to poll for mid-turn. So the app re-resumes the session the HUD
ended on, which rebinds the transport, reconciles the transcript, and picks up
an in-flight turn. Main carries the session id across, since it is the only
party that outlives the HUD's renderer.
2026-08-08 01:38:57 -05:00
Brooklyn Nicholson 8560dc6b97 feat(desktop): let a composer draft move between windows
Drafts are per-renderer state backed by shared localStorage, and the map is
read once at module load, so two windows on the same session diverge the
moment either types. Adds the two verbs a handoff needs: `reloadPersistedDrafts`
to merge another window's writes in (keeping local attachments, which are never
persisted), and a draft-sync bus so a composer can be told to flush its live
text down to the stash or repaint from it.

Dispatched synchronously, unlike the focus bus — a flush has to complete before
the window that will read it is created.
2026-08-08 01:38:57 -05:00
Brooklyn Nicholson 0db11f9952 fix(desktop): keep the transcript whole when resuming a running session
Resuming a session that is mid-turn somewhere else collapsed the thread down
to the in-flight prompt, and the user's own message never appeared at all
until a reload.

Two wrong premises in the resume path, neither specific to any one surface:

`omit_messages` was read as "empty transcript" rather than "no transcript in
this response". Desktop asks session.resume/activate to omit messages because
REST is the transcript authority, so mid-turn the live projection got
reconciled against an empty list and rebuilt the thread out of itself. The
response already carries `messages_omitted`; nothing read it. It now grafts
the projection onto the cache (or the REST prefetch) instead.

The settle path skipped hydration whenever the window had streamed the reply,
on the assumption that streaming a reply means owning the whole turn. True for
a turn you started, false for one you adopted: it arrives reply-first with no
prompt row, and nothing ever backfilled it. Sessions now carry
`adoptedRunningTurn`, set when a resume lands on an already-running turn and
consumed when it settles.
2026-08-08 01:38:49 -05:00
brooklyn! b9aa9289a8 Merge pull request #81414 from helix4u/fix/ssh-remote-tilde-cwd
fix(terminal): preserve SSH remote home cwd
2026-08-08 00:32:45 -05:00
Gille 9c69d98864 fix(terminal): preserve SSH remote home cwd 2026-08-07 18:41:57 -06:00
rob-maron b3aa561faf add Hermes headers to Fireworks provider (#81321) 2026-08-07 20:56:29 +00:00
kshitij a8c50eb1d8 fix: relax start_new_session assertion for systemd scope path
The windows-compat change-detector checked for the literal string
'start_new_session=True', but the systemd scope isolation path
conditionally uses start_new_session=False (the scope creates its own
session/cgroup). Assert 'start_new_session=' instead — the value may
now be a variable.
2026-08-08 01:12:16 +05:30
kshitij b5c2116783 fix: defer O(n) fallback_data construction to failure path in _save_entry
The entry_data/lock_held path (used by mark_turn_active/clear_turn_active)
was eagerly constructing a full O(n) snapshot of all routing entries under
the lock on every turn, even when the DB upsert would succeed.  Defer the
fallback_data construction to the except branch where it's actually needed,
and respect lock_held to avoid re-acquiring a non-reentrant _lock.
2026-08-08 01:12:16 +05:30
Dominic Bejar c5e032c804 fix(gateway): close ambiguous recovery cleanup gaps 2026-08-08 01:12:16 +05:30
Dominic Bejar 46b5314229 fix(terminal): harden scope fallback and memory override 2026-08-08 01:12:16 +05:30