Commit graph

15860 commits

Author SHA1 Message Date
k4z4n0v4
a35bc81b3f fix(honcho): honest, non-overlapping tool descriptions + drop dead param
The five Honcho tool schemas had overlapping/misleading descriptions, making it
hard for the model to pick the right one, plus two concrete correctness bugs:

- honcho_context advertised an 'Optional focus query' parameter that the dispatch
  never read. The model could pass query= expecting filtering that never happened.
  Remove the dead parameter; honcho_context is an honest no-query snapshot. Focused
  retrieval now lives in honcho_search (see prior commit).
- honcho_conclude's peer param was described as 'Peer to query' — wrong; it's the
  peer the conclusion is ABOUT. Corrected.

Rewrite all five descriptions to give each tool a distinct mental model and
cross-reference siblings:
  profile  = read/write the compact card (cheapest, no query, no LLM)
  search   = find what was actually said, ranked, cross-session (cheap, no LLM)
  reasoning= ask a question, get a synthesized answer (the only LLM tool; expensive)
  context  = a fixed session snapshot (no query, no LLM)
  conclude = write a durable fact to the profile

Addresses the honcho_context query param half of #29402 (see PR notes for the
divergence from that issue's proposed wire-through approach).
2026-07-16 12:48:48 -07:00
k4z4n0v4
c1c59e3474 fix(honcho): make honcho_search do real cross-session message search
honcho_search routed through search_context() -> peer.context(search_query=),
which returns the peer's standing representation + card. The search_query arg
does not turn that endpoint into a search, so results were effectively
query-independent: the same representation blob regardless of the query. Factual
lookups ('what medication', 'which value did we pick') returned noise.

Rewire search_context() to call the workspace message-search endpoint
(Honcho.search) with a peer_perspective filter: RRF-ranked (hybrid semantic +
full-text) raw message excerpts spanning every session the peer was a member of,
across all authors, membership-time-scoped. This is the cross-session factual
recall primitive.

peer_perspective is chosen over the alternatives because it is the only scope
that is simultaneously (a) cross-session, (b) inclusive of assistant-authored
facts about the peer (peer-author search drops these, and they are a large part
of what you want to recall about yourself), and (c) privacy-scoped to the peer's
own sessions (plain workspace search leaks other peers' sessions).

- snippets are labeled by author so the model can tell user-stated facts from
  assistant-derived ones
- max_tokens is now an enforced budget (was accepted but meaningless)
- graceful fallback to peer-authored search if peer_perspective is unsupported
- query length clamped under the embedding input cap

Replaces 3 change-detector tests that asserted the old representation-dump
behavior with 4 that assert the message-search contract + fallback path.
2026-07-16 12:48:48 -07:00
Teknium
31a3822b80
fix(title): contain auto-title thread exceptions instead of dumping tracebacks to the terminal (#65792)
auto_title_session runs as a bare daemon-thread target. Any exception
escaping it hits the default threading excepthook and sprays a raw
traceback into the user's terminal mid-session. The canonical trigger
is the post-'hermes update' stale-module window: the function's lazy
imports read NEW source from disk while already-imported modules
(agent.portal_tags) are still the OLD cached version, producing an
ImportError that repeats on every auto-title attempt until the
long-running process restarts (seen live after 9ce0e67f2 added
set_conversation_context).

The public entrypoint now wraps the body in a catch-all that logs one
WARNING naming the likely cause ('restart the running Hermes process'),
routes the exception through the existing failure_callback channel
(user-visible warning in CLI, debug-suppressed in gateway per #23246),
and never re-raises. This also makes the function honor its own
docstring contract ('silently skips if title generation fails').
2026-07-16 12:41:56 -07:00
ethernet
f1af945f6c
fix(desktop): slow session switch (#65890)
react-router v7's HashRouter wraps every route state update in
React.startTransition() by default. In React 19's concurrent renderer,
transitions are non-urgent — React can yield mid-render and come back
later. When the app is under load (streaming token deltas, gateway
events, store updates from active sessions), those higher-priority
updates keep interrupting the transition, starving the route change commit.

This matches every symptom of the session-switch lag:
- Main thread is free (animations run, clicks work) — startTransition
  defers, it does not block
- navigate() does not take effect for seconds — the transition keeps
  getting interrupted by higher-priority updates
- Worse under load — more concurrent re-renders = more interruptions
- The whole UI (sidebar + main pane) does not update — the entire route
  change is one transition, so nothing commits until it finishes

Pass useTransitions={false} to HashRouter so route state updates are
synchronous at default React priority instead of deferred transition
priority. navigate() now commits and paints immediately.

See: react-router v7 chunk-BIP66BKV.js HashRouter implementation.
2026-07-16 15:34:53 -04:00
Andry Lloyd Paez
b0ca12192e
fix(desktop): restore closed main window on second launch (#64800)
* fix(desktop): restore closed main window on second launch

* fix(desktop): reset deep-link readiness when main window closes
2026-07-16 15:33:38 -04:00
brooklyn!
7d27a31ce7
feat(dashboard): isolate turns in compute host (#65895)
Add the flag-gated compute-host supervisor, delta/control protocol, PPID orphan guard, inline fail-open path, synthetic GIL-heavy turn seam, and AC-4 certify harness.

Verified on current origin/main: 343 focused tests pass; Ruff and diff checks pass; 360s AC-4 run with six heavy lanes passes at 6.11ms serving p99 with zero stalls and valid load.

Co-authored-by: Kyzcreig <9063726+Kyzcreig@users.noreply.github.com>
2026-07-16 15:24:03 -04:00
brooklyn!
91ed8e4a99
Merge pull request #65893 from NousResearch/bb/salvage-63082-sessiondb-offload
fix(dashboard): offload blocking SessionDB handlers (supersedes #63082)
2026-07-16 15:23:32 -04:00
brooklyn!
2655c725cc
fix(desktop): refresh default-derived composer model (#65896)
Co-authored-by: Koho Zheng <koho.jung@outlook.com>
2026-07-16 15:23:13 -04:00
UnathiCodex
bcf0d74572
fix(desktop): preserve zoom across display moves (#65874)
Reassert the persisted webContents zoom after BrowserWindow moved, covering Windows monitor transitions where Chromium recalculates display scaling and drops the user-selected zoom.
2026-07-16 19:21:43 +00:00
Koho Zheng
0f6abc73a8 fix(desktop): refresh default-derived composer model 2026-07-16 15:15:36 -04:00
Gille
c387be08b9
fix(desktop): serialize git status refreshes (#65341) 2026-07-16 15:05:14 -04:00
brooklyn!
bed46fcd5c
Merge pull request #65885 from NousResearch/bb/salvage-62308-stale-backend
fix(desktop): preserve active connection across stale backend exits (supersedes #62308)
2026-07-16 15:01:37 -04:00
Brooklyn Nicholson
ee8275a8b2 test(desktop): port backend-connection-state test to vitest
Rebased onto current main, where the electron test harness migrated from
node:test to vitest (test:desktop:platforms = `vitest run --project electron`,
auto-discovering electron/*.test.ts). Swap the node:test import for vitest and
drop the .ts-extension import hack; the obsolete package.json node --test list
edit is dropped in the cherry-pick resolution since vitest auto-discovers.

Co-authored-by: Gille <4317663+helix4u@users.noreply.github.com>
2026-07-16 14:55:58 -04:00
Gille
71fa56e8ac fix(desktop): restore cloud reconnect action 2026-07-16 14:53:11 -04:00
Gille
783003179a fix(desktop): ignore stale backend exits 2026-07-16 14:53:11 -04:00
geoffreybutler94
f0ff8d5097
fix(desktop): preserve routed session on profile rebind (#65283)
Co-authored-by: geoffreybutler94 <257877469+geoffreybutler94@users.noreply.github.com>
2026-07-16 18:33:00 +00:00
Teknium
bd37ff9138
feat(gateway): inline choice pickers for /reasoning and /fast (Telegram, Discord, Matrix) (#65799)
Bare /reasoning and /fast now render a native one-tap picker on
picker-capable platforms, with automatic fallback to the text status
card everywhere else — parity with the /model picker UX.

- gateway/slash_commands.py: generic send_choice_picker capability gate
  (detected on the adapter type, like send_model_picker); selection and
  typed arguments flow through one shared application path so they can
  never diverge; choices built from VALID_REASONING_EFFORTS so future
  levels appear automatically
- telegram: flat inline-keyboard picker (cp:<idx> callbacks), authorized
  users only (same gate as approval buttons)
- discord: ChoicePickerView select menu, auth mirrors ExecApprovalView,
  2-minute timeout with expiry edit
- matrix: reaction-based picker; reaction set extended to 12 slots to
  fit the full effort ladder + subcommands
- locales: picker_title + choice labels in all 16 languages
- docs: ADDING_A_PLATFORM.md capability table

Closes #61110.
2026-07-16 10:38:31 -07:00
ethernet
659d1123c4
fix(desktop): model picker reverts in existing threads (#65777)
selectModel in use-model-controls captured activeSessionId as a closure
prop, but the actions bag in wiring.tsx mutates in place to keep a stable
identity for memoized surfaces. The modelMenuContent useMemo captures
selectModel once when the gateway first opens (before any session is
active) and never re-evaluates, so clicking a model in an existing thread
goes through a stale closure with activeSessionId=null — the pick is
treated as UI-only, config.set is never sent, and the next session.info
event clobbers the optimistic update back to the session's real model.

Drop the activeSessionId prop entirely. All three callbacks now read
$activeSessionId.get() live from the store, matching the pattern
refreshCurrentModel already followed. This is the correct contract for
the actions bag's in-place mutation: callbacks read live state from the
store, not from captured props.
2026-07-16 16:17:41 +00:00
Teknium
6cd5a2c5f7 chore(release): add sam7894604 to AUTHOR_MAP; widen /reasoning choices to max/ultra
The salvaged choices list predates the max/ultra effort levels (#62650);
add them so the Discord dropdown matches the canonical ladder. Discord
caps choices at 25 — we're at 11, plenty of headroom.
2026-07-16 08:58:34 -07:00
Sam Liu
bfca45bda0 fix(discord): expose /reasoning reset|show|hide as slash choices
The Discord /reasoning command declared a single free-text `effort`
parameter, so the native UI funneled every invocation into that one box
and never surfaced the reset / show / hide subcommands the gateway
handler already supports. Replace the free-text param with an explicit
choices dropdown covering the effort levels plus reset/show/hide,
mirroring the existing /tokens and /voice commands. --global persistence
stays reachable by typing the command as plain text.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 08:58:34 -07:00
Teknium
23526148d1 fix(terminal): bridge terminal.backend config in serve/desktop processes lacking a launcher env bridge
terminal_tool reads all settings from TERMINAL_* env vars, bridged from
config.yaml by the CLI, gateway, and TUI-PTY launchers. Processes that
skip every launcher bridge — hermes serve / the Desktop app backend's
in-process agents, the desktop cron ticker — saw an unset TERMINAL_ENV
and silently ran every command on the host even when config.yaml selects
terminal.backend: docker. A user who configured Docker isolation got
unsandboxed host execution with no warning.

Two layers:
- _ensure_terminal_env_bridged() in _get_env_config(): when TERMINAL_ENV
  is unset, backfill TERMINAL_* from config.yaml via
  apply_terminal_config_to_env(override=False). Explicit env always wins
  (honor explicit choice; only fix the accidental fallback). One-shot,
  fail-open to the historical local default.
- cmd_dashboard/serve: run the same bridge at startup so every consumer
  in the backend process (in-process agents, desktop cron ticker,
  tui_gateway cwd resolution) sees the bridged env directly.

Fixes #63141, #54449, #61115, #65696.
2026-07-16 08:55:10 -07:00
Sahil-SS9
fdbfae825e fix(tui): fall back to config terminal.backend when TERMINAL_ENV is unset in dashboard/TUI process (#54449) 2026-07-16 08:55:10 -07:00
Teknium
d0dcb9a5fd
fix(update): consolidate pre-update backups into one gated mechanism (#65754)
hermes update ran TWO separate pre-update backup mechanisms: the
config-gated full zip (updates.pre_update_backup, default off) and an
unconditional quick state snapshot added for #15733 that ignored the
user's setting entirely. On a large state.db (observed: 24 GB) the
'cheap' snapshot silently added ~60s to every update and ate 24 GB of
disk in state-snapshots/.

Now there is ONE mechanism, gated by updates.pre_update_backup with
three modes:

- quick (new default): state snapshot of critical small files (pairing
  JSONs, cron jobs, config, auth, per-profile DBs). Files over 1 GiB
  are skipped with a warning so a bloated state.db can never stall the
  update again.
- full: the quick snapshot plus the HERMES_HOME zip (old 'true'
  behavior; --backup forces it for one run).
- off: nothing runs — an explicit opt-out now disables the quick
  snapshot too (--no-backup does the same per-run).

Legacy booleans are honored: true -> full, false -> off.

_run_pre_update_backup() now returns the quick-snapshot id so the
post-update cron-jobs restore safety net (#34600) keeps working; the
snapshot moved from the post-fetch site to the pre-mutation site,
which also covers the zip-fallback update path it previously missed.
2026-07-16 08:47:25 -07:00
Teknium
8462764367 chore(release): map bare-noreply test-commit identity for PR #62028 salvage 2026-07-16 08:47:10 -07:00
Bryan Nathan
b099652d9f test(copilot): cover supported xhigh request paths
Add current-main regression coverage for both the registered provider
profile and core GitHub Responses path while leaving live catalog loading
to the complementary catalog-resolution work in #51953.
2026-07-16 08:47:10 -07:00
arminanton
cf73b3d411 fix(copilot): clamp reasoning effort to the nearest supported level, not xhigh->high
The Copilot provider profile unconditionally mapped ``xhigh`` to ``high`` before
checking the model's catalog, so models that DO support ``xhigh`` (e.g. the
gpt-5.x family per the live /models catalog) were silently capped one level
down.

Honor the requested effort when the catalog lists it as supported, and only
downgrade when it does not, choosing the nearest weaker supported level
(xhigh->high, minimal->low, else medium, else the first supported level). This
matches the nearest-down clamp behavior used elsewhere for the ``max`` effort.

Adds tests/plugins/model_providers/test_copilot_profile.py covering forward,
downgrade, and fallback paths (catalog lookup stubbed).
2026-07-16 08:47:10 -07:00
Al Sayed Hoota
633fc7ab88 fix: don't downgrade xhigh reasoning effort when provider supports it
The current code unconditionally downgrades 'xhigh' to 'high' whenever
'high' is in supported_efforts, even if 'xhigh' is also supported.
This prevents users from using extended thinking on providers like
Copilot that list 'xhigh' in their supported efforts.

Fix: only downgrade 'xhigh' to 'high' when 'xhigh' is NOT in the
provider's supported efforts list.
2026-07-16 08:47:10 -07:00
kshitij
0678f8f019
fix(desktop): force npm --include=dev so self-update rebuild can't be broken by NODE_ENV=production (#38416)
The desktop self-update rebuild (`hermes desktop --build-only`, driven by
the macOS in-app updater) runs `_run_npm_install_deterministic`, which used
plain `npm ci` / `npm install`. Those honor an inherited
`NODE_ENV=production` (or npm `omit=dev`) and silently omit devDependencies.

The desktop build toolchain — tsc, vite, electron-builder — are all
devDependencies, so under `NODE_ENV=production` the install completes (exit 0)
but `tsc` is never placed, and the very next step (`tsc -b && vite build`)
dies with `tsc: command not found` (exit 127). The user sees
"UPDATE DIDN'T FINISH / Rebuilding the desktop app failed (exit 127)" even
though the git/pip update applied cleanly. NODE_ENV=production can leak in
from a shell profile, a parent process, or a packaged-app launch context.

Force `--include=dev` on both the `npm ci` and `npm install` paths. The only
callers are frontend builds (desktop / TUI / web), which always need the dev
toolchain, so this is safe across the board.

Verified empirically: under NODE_ENV=production, `npm ci` leaves tsc MISSING
while `npm ci --include=dev` installs it. Added two regression tests
(test_npm_ci_forces_include_dev, test_npm_install_fallback_forces_include_dev)
— both confirmed to FAIL when the fix is reverted.
2026-07-16 15:45:07 +00:00
Teknium
007cd15132 chore(release): map focusedmiqa@gmail.com to m1qaweb in AUTHOR_MAP (PR #29290 salvage) 2026-07-16 08:02:47 -07:00
miqaeli
9298099689 fix(gateway): strip /queue prefix when no agent is running
When no agent is active, '/queue <prompt>' previously fell through
dispatch with its raw text intact instead of being treated as a
normal prompt. Rewrite event.text to the bare payload (mirroring the
/steer no-active-agent path just below) and return a usage hint when
the payload is empty.

Salvaged from PR #29290 (queue half only — the /footer mid-run
dispatch half already landed on main via #65521).
2026-07-16 08:02:47 -07:00
kshitijk4poor
5d9a72b7c2 fix(ollama-cloud): capability-gate reasoning_effort + correct disable semantics
Three follow-up fixes to the salvaged reasoning_effort support, all verified
live against ollama.com /v1/chat/completions + /api/show on deepseek-v4-pro,
gemma3, and qwen3-coder:

1. Capability-gate on /api/show 'thinking'. The original ignored the
   supports_reasoning flag and emitted reasoning_effort for every model. Now
   gated: only models whose native /api/show capabilities list contains
   'thinking' (deepseek-v4 yes; gemma3 / qwen3-coder no) get reasoning_effort.
   Mirrors the LM Studio pattern — capability resolved once per (model,
   base_url) in run_agent._supports_reasoning_extra_body via a cached probe
   (hermes_cli.models.ollama_model_supports_thinking), threaded into the
   profile hook as supports_reasoning. No live HTTP in the per-request path.

2. Disable actually disables. Ollama Cloud defaults to thinking ON and IGNORES
   the extra_body.thinking:{type:disabled} shape (verified: still returned
   reasoning). The only working off switch is top-level reasoning_effort:'none'.
   The salvaged code returned ({}, {}) for enabled:false / effort:none, leaving
   thinking ON. Now emits {'reasoning_effort': 'none'}.

3. Omit unrecognized effort. The original forwarded any unknown string verbatim
   including 'minimal' (a real Hermes effort level). Ollama Cloud rejects
   unrecognized values with a hard HTTP 400 (accepted set: low/medium/high/
   max/none), so forwarding 'minimal' would break the request. Now omitted.

Core touches (run_agent.py, hermes_cli/models.py) add the capability probe;
the plugin profile only consumes the resolved flag. 24/24 profile tests green;
194 provider/transport tests unaffected.
2026-07-16 07:58:04 -07:00
Teknium
4759362188 chore(release): add briandevans to AUTHOR_MAP for PR #64951 salvage 2026-07-16 07:57:51 -07:00
briandevans
9078a838c7 fix(lmstudio): clamp max/ultra reasoning effort to LM Studio's ceiling
LM Studio's request vocabulary tops out at "xhigh", but Hermes' generic
effort ladder has since grown two stronger levels. "max" and "ultra" miss
the _LM_VALID_EFFORTS membership test, keep the initialized "medium"
default, and are thereby conflated with unparseable input -- so asking for
more reasoning yields less than "xhigh":

    high  -> 'high'     xhigh -> 'xhigh'
    max   -> 'medium'   ultra -> 'medium'

This is drift, not a design choice. The valid set was an exact mirror of
VALID_REASONING_EFFORTS when the file was authored; the ladder then grew
"max" and later "ultra", and the sweep that taught every other provider
about the new levels missed this module -- it has never been touched since
it was written.

Clamp the two stronger levels onto LM Studio's declared ceiling instead,
mirroring the ceiling clamp every other provider already applies. Widening
_LM_VALID_EFFORTS would instead assert that LM Studio accepts "max" on the
wire, which is a provider-side claim this repo cannot verify; clamping
consumes only the ceiling the file already declares for itself.

The clamp is kept separate from _LM_EFFORT_ALIASES because that mapping is
also applied to the model's published allowed_options, which must not be
rewritten. A clamped value stays subject to the allowed_options check, so a
model that does not publish "xhigh" still gets the field omitted and falls
back to its own default -- exactly how a directly-requested "xhigh" behaves.

The regression test asserts monotonicity over the canonical ladder rather
than the two values alone, so the next level added upstream cannot silently
reintroduce the inversion.
2026-07-16 07:57:51 -07:00
Teknium
d79f75e1c6 test: use object.__new__ runner pattern for background-task scope tests
Replaces the salvaged tests' dict-config path (which required a
GatewayRunner.__init__ dict-coercion hack — dropped from this salvage;
the second commit on the PR branch existed only to support it) with the
established bare-runner test pattern. Also asserts full argument
passthrough to the inner task.
2026-07-16 07:57:06 -07:00
liuhao1024
8091c44054 fix(gateway): install _profile_runtime_scope in _run_background_task when multiplexing is active
When multiplex_profiles is true, background tasks spawned by /background
command failed with UnscopedSecretError because _resolve_session_agent_runtime()
was called without a profile secret scope. This fix wraps the task in
_profile_runtime_scope, mirroring the pattern used by _run_agent.

Fixes #60726
2026-07-16 07:57:06 -07:00
Tim Roth
58010c8b3d fix(mcp): reuse cached oauth redirect port 2026-07-16 07:56:46 -07:00
Teknium
b5bd0ef38b docs(kanban): port attachment guidance into KANBAN_GUIDANCE
PR #36019 documented the attachment tools in the kanban-worker skill,
but main removed that skill in #50473 and folded its content into the
KANBAN_GUIDANCE prompt block. Land the same guidance there instead so
every dispatcher-spawned worker sees it.
2026-07-16 07:33:14 -07:00
Teknium
6cc4691c86 chore(release): map otsune's noreply email in AUTHOR_MAP (PR #36019 salvage) 2026-07-16 07:33:14 -07:00
Teknium
c2e11bf418 fix(kanban): guard kanban_attach_url against SSRF via tools.url_safety
_download_url_with_cap called urlopen() after only a scheme check, so a
model-controlled URL could reach loopback services, RFC1918/CGNAT hosts,
or cloud metadata endpoints (169.254.169.254), and a public host could
302 to any of those unvalidated.

Route the fetch through the repo's canonical SSRF guard instead:
validate every hop with tools.url_safety.is_safe_url() and follow
redirects manually (httpx, follow_redirects=False, 5-hop limit) so each
Location target is re-checked before it is fetched — the same pattern
as tools/skills_hub._guarded_http_get. The streaming size cap is
unchanged. Local-fixture tests opt in via HERMES_ALLOW_PRIVATE_URLS
(the guard's documented escape hatch); new tests pin rejection of
loopback, cloud-metadata, and private-range URLs, a mocked
public→loopback redirect, and a mocked public happy path.
2026-07-16 07:33:14 -07:00
Teknium
f3cbe45605 refactor(kanban): unify attachment size cap on KANBAN_ATTACHMENT_MAX_BYTES
The salvaged attachment-toolset commit predated main centralizing the
25 MB cap as kanban_db.KANBAN_ATTACHMENT_MAX_BYTES and re-introduced a
private _MAX_ATTACHMENT_BYTES alias. Drop the duplicate: kanban_db's
store_attachment_bytes(), the dashboard upload endpoint, and the
kanban_attach_url tool all reference the one shared constant now, and
the tests monkeypatch that same name.
2026-07-16 07:33:14 -07:00
otsune
3fccd698fd feat(kanban): attachment toolset + CLI to match the dashboard surface
The kanban board has had full attachment storage and a dashboard HTTP
API (upload/list/download/delete) since #35338, but there was no agent
toolset tool and no `hermes kanban` CLI verb for attachments. Agents and
scripts that don't go through the dashboard server (or can't touch the DB
directly) had no way to create or read real attachments — only links in
comments.

Close that gap by mirroring the existing comment surface:

- `kanban_db.store_attachment_bytes()` — one shared write path (validate
  name, enforce the 25 MB cap, write the blob under the per-task dir with
  collision-free naming, insert the metadata row, clean up an orphan blob
  if the insert fails). `_MAX_ATTACHMENT_BYTES`, `_safe_attachment_name`,
  and a new `_collision_free_path` move here so the dashboard, the tool,
  and the CLI all share one implementation and can't drift.
- Tools (`tools/kanban_tools.py`): `kanban_attach` (inline base64),
  `kanban_attach_url` (server-side http/https fetch with the same cap),
  `kanban_attachments` (list). Write tools respect worker task-ownership;
  list is read-only. Registered in the `kanban` toolset.
- CLI (`hermes_cli/kanban.py`): `attach <id> <path>`, `attachments <id>`,
  `attach-rm <attachment_id>`.
- Dashboard `upload_task_attachment` now imports the shared helpers and
  uses `_collision_free_path` — behavior identical (still streams to disk
  with the cap, still 413 on overflow).
- Docs (AGENTS.md, kanban-worker skill) and toolset membership updated.

Tests: tool round-trip + oversize + bad base64 + ownership; attach_url
against a local HTTP fixture incl. oversize-mid-stream and non-http
scheme rejection; CLI attach/attachments/attach-rm; shared-helper unit
tests; dashboard parity preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 07:33:14 -07:00
Teknium
14f023cd00 fix(api_server): run platform event verifiers off-loop and fail closed
The platform callback verifier can do blocking network I/O (e.g. the
google-chat adapter fetches Google signing certs on a cache miss), which
would stall the event loop if called inline. Run sync verifiers via
asyncio.to_thread (await coroutine verifiers directly), and treat a
crashing verifier as a 401 rather than a 500 through the dispatch path —
a broken verifier must never admit an event.
2026-07-16 07:31:05 -07:00
aeyeopsdev
a7ec1b6e39 fix(google-chat): cache callback token cert fetches 2026-07-16 07:31:05 -07:00
aeyeopsdev
1305a690e0 feat(gateway): route platform HTTP event callbacks 2026-07-16 07:31:05 -07:00
Teknium
03c0b00f45
fix(usage): read DeepSeek's native prompt_cache_hit_tokens cache field (#65678)
Some checks are pending
CI / Detect affected areas (push) Waiting to run
CI / Python tests (push) Blocked by required conditions
CI / Python lints (push) Blocked by required conditions
CI / JS & TS checks (push) Blocked by required conditions
CI / Docs Site (push) Blocked by required conditions
CI / Deny unrelated histories (push) Blocked by required conditions
CI / Check contributors (push) Blocked by required conditions
CI / Check uv.lock (push) Blocked by required conditions
CI / package-lock.json diff (push) Blocked by required conditions
CI / Lint Docker scripts (push) Blocked by required conditions
CI / Build&Test Docker image (push) Blocked by required conditions
CI / Supply-chain scan (push) Blocked by required conditions
CI / OSV scan (push) Waiting to run
CI / All required checks pass (push) Blocked by required conditions
CI / CI timing report (push) Blocked by required conditions
Deploy Site / deploy-vercel (push) Waiting to run
Deploy Site / deploy-docs (push) Waiting to run
auto-fix lint issues & formatting / Generate eslint --fix patch (push) Waiting to run
auto-fix lint issues & formatting / Apply patch (push) Blocked by required conditions
DeepSeek's own API (api.deepseek.com) reports context-cache hits as
top-level usage.prompt_cache_hit_tokens / prompt_cache_miss_tokens
(prompt_tokens = hit + miss), not the OpenAI nested
prompt_tokens_details.cached_tokens shape. Neither normalize_usage()
nor the chat_completions transport's extract_cache_stats() read those
fields, so direct DeepSeek sessions always showed 0 cache-hit tokens:
invisible in accounting, mis-billed at the full input rate, and 0%
cache display.

Both layers now fall back to prompt_cache_hit_tokens when the nested
shape is absent; the nested value wins when both are present (proxies).

Fixes #61871.
2026-07-16 07:29:53 -07:00
Teknium
7edaaf4682 chore: map nnnet noreply email in release AUTHOR_MAP (PR #36024 salvage) 2026-07-16 07:28:22 -07:00
Teknium
558fcb6146 test(dashboard): cover theme bootstrap CSS render + _serve_index injection
Server-side coverage for the critical-CSS shim (PR #36024 salvage):

- user theme → style block emitted with ONLY real bundle variable names
  (--background-base/--midground-base from layerVars(),
  --theme-font-sans/--theme-base-size from typographyVars()/index.css),
  and an html,body rule expressed via those vars so runtime theme
  switches never leave a stale canvas/font
- built-in / unknown / non-string active theme → no block
- malformed theme YAML and load_config() exceptions → no crash, index
  still serves
- </style> breakout attempt in a theme value stays escaped
- mount_spa integration: block present in <head> for user themes,
  absent for built-ins
2026-07-16 07:28:22 -07:00
Teknium
01bab394cd fix(dashboard): theme bootstrap emits real bundle CSS vars; canvas rule flows through vars
Review fixes for the inline critical-CSS bootstrap (PR #36024):

1. Variable names now match what the bundle actually consumes.
   --color-background, --color-midground, --font-sans and
   --font-base-size appear nowhere in web/src; the real tokens are:
     --background-base / --midground-base  (layerVars(), context.tsx)
     --theme-font-sans / --theme-base-size (typographyVars(), and
       index.css html{font-family:var(--theme-font-sans);
       font-size:var(--theme-base-size)})

2. Stale-rule bug: the injected html,body rule previously baked in
   literal hex/font values. Because the <style> block sits after the
   bundle's <link> at equal specificity and is never removed, switching
   themes in the picker left the old canvas/font until reload. The rule
   now references the same CSS variables instead of literals —
   applyTheme() writes those vars as inline styles on documentElement,
   which outrank this block in the cascade, so runtime theme switches
   re-resolve the rule automatically. No frontend change needed.
2026-07-16 07:28:22 -07:00
nnnet
72562be961 fix(dashboard): inline critical-CSS bootstrap for user themes to mitigate flash
User themes (`~/.hermes/dashboard-themes/*.yaml`) reach the SPA only
after `/api/dashboard/themes` resolves at React mount.  The bundle paints
the first frame with the default Hermes Teal canvas — the
`<link rel="stylesheet">` carries `:root{--background-base:#041c1c}`,
the bundled `presets.ts` defines the same surfaces in JS — and then
`ThemeProvider.applyTheme(<user theme>)` flips the inline CSS variables
on `documentElement` once the API response lands.  Visible to the user
as a green canvas behind the loading SPA on every reload when the active
theme is non-default.

Built-in themes do not suffer the same effect because their full
definitions ship inside the bundle, so the SPA already has the palette
before first paint.

This patch closes the gap on the backend side: `_serve_index()` injects
a `<style id="hermes-theme-bootstrap">` block inside `<head>` with the
six critical CSS variables (`--background-base`, `--color-background`,
`--midground-base`, `--color-midground`, `--font-sans`,
`--font-base-size`) plus an `html, body` rule painting the body in the
target palette.  Because the inline `<style>` follows the bundle's
`<link>` in DOM order and matches the same `:root` specificity, the
later declaration wins the cascade — the static canvas behind the SPA is
already the right colour before any JavaScript runs.

`_render_active_theme_bootstrap_css()` looks up the active theme through
the existing `_discover_user_themes()` helper.  No-op for built-in
active themes (empty string returned, no `<style>` injected).  No new
API endpoints, no config flags, no frontend changes.

After `ThemeProvider` mounts and `applyTheme()` writes the same
variables as inline styles on `documentElement`, the values match what
the bootstrap block set, so there is no second-paint discrepancy on the
critical CSS variables.
2026-07-16 07:28:22 -07:00
Teknium
2fba721ab0 chore(release): map antydizajn's commit email for PR #36043 salvage attribution 2026-07-16 07:28:07 -07:00