Commit graph

11860 commits

Author SHA1 Message Date
kshitijk4poor
ca6542f602 docs(cli): note URL exclusion in _extract_path_word docstring
The docstring described a token as path-like when it contains a "/"
separator, but the keystroke-latency fix now excludes "://" scheme tokens
(URLs) even though they contain "/". Document the exclusion so the contract
matches the behavior.
2026-06-17 12:36:01 +05:30
kshitijk4poor
fbaad3031a test(cli): URL tokens must not trigger filesystem path completion
Regression coverage for the keystroke-latency fix: a URL token contains
"/", so the bare-slash path heuristic used to return it as a path word and
run os.listdir on every keystroke. Assert _extract_path_word rejects
http/https/ssh scheme tokens, that ordinary paths (incl. a bare colon) are
unaffected, and that the completer never touches the filesystem for a URL
under the cursor.
2026-06-17 12:33:56 +05:30
xxxigm
f48b312037 fix(cli): keep typing responsive by not blocking the keystroke loop
The interactive CLI input box runs its completer with
`complete_while_typing=True`, so `SlashCommandCompleter.get_completions`
is invoked on *every* keystroke. That completer does blocking I/O:
fuzzy `@`-file indexing shells out to `rg`/`fd` (up to a 2s timeout) and
file-path completion calls `os.listdir` + `stat`. Because the completer
was passed inline (never wrapped in `ThreadedCompleter`), all of this ran
synchronously on the prompt_toolkit event loop, stalling the render after
each key — very noticeable on WSL2 and other slow-filesystem setups
("typing in the prompt box being very latent").

Two fixes:

- Wrap the input completer in `ThreadedCompleter` so completion work runs
  off the UI event loop and never blocks rendering between keystrokes.
- Stop treating URLs as file paths in `_extract_path_word`: a token like
  `https://example.com/x` contains `/`, so it triggered `os.listdir` on
  every keystroke while typing/pasting a link (listing a bogus `https:`
  dir) for a completion that can never be useful. Skip any token with a
  `://` scheme separator.

(cherry picked from commit b5be2ba276)
2026-06-17 12:32:38 +05:30
Bartok
5e01a5dbf1
fix(cli): detect containerd/CRI cgroup-v2 containers in is_container() (#47131)
Closes #47111

is_container() only recognized Docker (/.dockerenv), Podman
(/run/.containerenv), and docker/podman/lxc markers in /proc/1/cgroup.
Under cgroup v2 (Kubernetes/k3s on containerd or CRI-O) /proc/1/cgroup
collapses to a single "0::/" line with no runtime marker, so
is_container() returned False on every containerd/CRI pod.

That false negative bypassed container-aware behavior across the CLI.
The most damaging case (reported): even after #46290 fixed
detect_service_manager() to gate on _s6_running() alone, other
is_container() call sites (profile home resolution, gateway behaviors,
config, doctor) still misbehave on containerd.

Broaden detection conservatively:
- KUBERNETES_SERVICE_HOST env var (present in every k8s pod).
- kubepods/containerd/crio markers in /proc/1/cgroup (cgroup v1 nested).
- same markers in /proc/self/mountinfo as a cgroup-v2 fallback.

Tests: 3 new (k8s env, kubepods cgroup, cgroup-v2-via-mountinfo) plus the
existing negative case hardened to stub mountinfo + env; 108 constants +
service_manager tests pass.
2026-06-17 12:11:31 +10:00
teknium
36ae958473 feat(gateway): gate message timestamps behind opt-in (default off)
Follow-up to salvaged PR #41633: the timestamp prefix injection was
unconditional. Gate the in-context render behind
gateway.message_timestamps.enabled (default false) at both the live-message
and history-replay sites; timestamp metadata is still captured + persisted
regardless so the toggle can be flipped on later. Add DEFAULT_CONFIG entry,
docs, and gate tests.
2026-06-16 15:49:59 -07:00
Wolfram Ravenwolf
bd7fc8fdcd feat(gateway): inject stable human-readable message timestamps
Consolidates these related Amy fork patches:
- 429830f39 feat(gateway): inject message timestamps into user messages for LLM context
- 3c3d6fac0 fix: handle both ISO string and epoch float timestamps in history replay
- 2874f7725 feat: human-friendly timestamp format with weekday and timezone name
- 3735f4c8b fix: render gateway message timestamps once
2026-06-16 15:49:59 -07:00
brooklyn!
b7f0c9cd52
fix(desktop): honor pre-session model pick + restore global reasoning/speed defaults (#47447)
* fix(desktop): keep the pre-session model pick selected in the picker

The composer picker derived its "current" row from `model.options ?? store`,
so model.options always won. Pre-session that query returns the PROFILE
DEFAULT, not the sticky composer pick — so selecting a model before a session
exists left the checkmark (and the picker's "current" line) on the default,
making the pick look ignored even though the pill updated.

Add `currentPickerSelection()`: with a live session the gateway's model.options
is authoritative; pre-session the sticky `$currentModel`/`$currentProvider`
wins, falling back to options. Wire it into ModelMenuPanel and ModelPickerDialog.

* feat(desktop): global reasoning/speed defaults in Settings → Model

The composer picker is now sticky-UI/per-session only and never writes the
profile default (#46959), but Settings → Model had no reasoning/speed control
and `agent.reasoning_effort` wasn't in the curated config surface at all
(`service_tier` was buried in Advanced) — so there was nowhere to set the
profile default that crons/subagents/messaging resolve from.

Add capability-gated Reasoning (effort) + Fast controls beside the main model,
gated by the applied model's reported capabilities (reasoning defaults on, fast
off when unreported — same as the composer). They read/write `agent.reasoning_effort`
and `agent.service_tier` by round-tripping the config record, matching the
gateway's value semantics (service_tier "fast"/"priority"/"on" ⇒ fast).

* refactor(desktop): don't open the reasoning select from its row label

A <label> wrapping the Select forwarded text clicks to the trigger, opening
the dropdown unexpectedly. Plain row for reasoning; Fast stays a <label> so
clicking its text toggles the switch (expected for a checkbox-like control).
2026-06-16 16:22:09 -05:00
xxxigm
d1ecebcbfd
fix(desktop): re-download Electron binary via mirror when pack fails (#47266) (#47276)
* fix(desktop): re-download Electron binary via mirror when pack fails (#47266)

Since #38673 pinned build.electronDist to node_modules/electron/dist,
electron-builder reads the Electron binary straight from there and never
downloads it during `npm run pack`. That dist tree is only produced by the
electron package's postinstall (install.js) during `npm ci`. When that
download is blocked or throttled (GitHub's release host is unreachable in
some regions), the dist is missing and the build dies with:

    The specified electronDist does not exist: .../node_modules/electron/dist

The existing ELECTRON_MIRROR fallback in all three desktop-build paths
(scripts/install.ps1, scripts/install.sh, and `hermes desktop` in
hermes_cli/main.py) re-ran `npm run pack` with ELECTRON_MIRROR set — but
pack never downloads Electron anymore, so the mirror was never used and the
retry re-read the same missing dist. The fallback was effectively dead.

Drive the mirror through electron's own downloader instead:

- Add a dist-presence check + a downloader helper (Test-ElectronDist /
  Restore-ElectronDist, _electron_dist_ok / _restore_electron_dist,
  _electron_dist_ok / _redownload_electron_dist) that wipes a partial dist
  + the path.txt version marker (electron's install.js short-circuits on it)
  and re-runs `node install.js`, optionally via a mirror.
- On the first retry, repopulate a missing dist from the canonical source;
  on the mirror retry, re-fetch through npmmirror.com, then pack.
- Gate the re-download on the dist check so an unrelated build failure
  (tsc/vite) doesn't trigger a pointless ~200 MB refetch, and skip the final
  pack when the binary still can't be fetched instead of failing the same way.

* test(desktop): cover Electron dist re-download mirror fallback (#47266)

Add behavior coverage for the electronDist re-download fix:

- _electron_dist_ok across linux/win32/darwin, including the partial-dist
  case (dir present but binary missing) that makes the pinned electronDist
  fail.
- _redownload_electron_dist: no-op when the binary is present, bail when
  install.js is absent, wipe a stale dist + path.txt marker and run
  electron's downloader with ELECTRON_MIRROR injected, and report failure
  when the download still produces no binary.
- `hermes desktop`: the mirror fallback now drives electron's own downloader
  before re-running pack, and skips the final pack entirely when the binary
  can't be fetched.

Replaces the old mirror test that asserted the (now-fixed) dead behavior of
re-running `npm run pack` with ELECTRON_MIRROR set — pack never downloads
Electron under the pinned electronDist, so that retry could never help.
2026-06-16 15:40:55 -05:00
teknium1
db44af004c test(model-picker): cover two overlapping user-defined custom providers
Guards that two user-defined custom endpoints exposing an overlapping
model each keep their full catalog — the dedup must never cross-filter
two user-defined rows against each other.
2026-06-16 13:09:40 -07:00
liuhao1024
1b962f001e fix(models): pass model.base_url to fetch_models in /model picker
The /model interactive picker resolved a base_url from user credentials
but never passed it to ProviderProfile.fetch_models(), causing the
picker to always query the provider's hardcoded default endpoint
instead of the user's custom URL (e.g. a company litellm proxy).

- providers/base.py: add optional base_url parameter to fetch_models()
- hermes_cli/models.py: pass resolved base_url to fetch_models()
- Update all subclass overrides for signature compatibility
- Add 6 regression tests covering override, fallback, and integration
2026-06-16 13:09:40 -07:00
Wolfram Ravenwolf
9137b86a52 fix(skills): ignore support docs in skill discovery
Support files under references/, templates/, assets/, and scripts/ are progressive-disclosure data loaded through skill_view(..., file_path=...). They should not be treated as standalone skills during discovery or collision checks.

This prevents archived skill packages or support markdown files inside a real skill from shadowing active skills with the same name while still allowing top-level categories named scripts/templates/assets/references.

Tests cover:
- pruning nested SKILL.md files inside skill support directories
- preserving support-named top-level categories
- avoiding skill_view collisions from support markdown
- keeping archived package SKILL.md files accessible only through file_path
2026-06-16 13:08:34 -07:00
teknium1
7493de7fc3 test(model-switch): cover section-3 no-auth probe; map chimpera author
Salvage follow-up for PR #29575: add regression tests for the section-3
no-api_key /v1/models probe (probes bare endpoints, skips when explicit
models set) and add the contributor AUTHOR_MAP entry.
2026-06-16 13:07:52 -07:00
chimpera
1039e90b5e fix(model-switch): probe /v1/models for providers without api_key
Section 3 of list_authenticated_providers (user-defined endpoints from
the providers: config section) required an api_key before probing the
endpoint's /v1/models for live model discovery. This broke local
self-hosted backends (llama.cpp, Ollama, vLLM, etc.) that don't require
authentication — they would only ever show the single default_model
from config instead of the full model catalog.

Section 4 (custom_providers list) already handled this correctly with
the policy: probe when api_key is set OR when no explicit models are
configured. Apply the same logic to Section 3 so local backends get
full model discovery without requiring a placeholder api_key workaround.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-16 13:07:52 -07:00
teknium1
8ed16a7a0c test(telegram): rich-reply recovery via send-time index
Cover #47375 fix: record-on-rich-send + lookup-on-reply round trip,
lookup miss leaving reply_to_text None, and precedence (native quote
and echoed caption both win over the index fallback).
2026-06-16 13:04:20 -07:00
teknium1
3f80bcac56 chore(release): AUTHOR_MAP entry for x1erra (Sierra) 2026-06-16 13:04:20 -07:00
Sierra (Hermes Agent)
01ae9b853e fix(telegram): resolve replies to rich (sendRichMessage) messages
Telegram does not echo a sendRichMessage's content back in
reply_to_message (.text/.caption empty, .api_kwargs None), so replies
to rich sends (briefings, the gateway's own rich finals) arrived with
no quotable text and the [Replying to: ...] injection was skipped.

Remember message_id -> text at send time in a best-effort JSON index
(gateway/rich_sent_store.py), and recover it on inbound when text and
caption are both empty. Best-effort and no-throw throughout: any
failure degrades to prior behavior and never breaks a send or message.

Salvaged from #47375 by @x1erra. Dropped the cross-platform run.py
reply-prefix rewrite (out of scope; bloated every reply on every
platform) and scrubbed a docstring reference to an out-of-repo script.
Kept the inbound reply_to logging enrichment used to verify the fix.
2026-06-16 13:04:20 -07:00
teknium1
db01910e3a chore(release): map cyb0rgk1tty noreply email for AUTHOR_MAP
Salvage follow-up for PR #46921 — CI matches contributor authorship on the
commit email, which is the GitHub noreply form.
2026-06-16 13:04:07 -07:00
cyb0rgk1tty
b7fa62c530 fix(inventory): keep user-defined custom providers in model dedup
The #45954 model-dedup builds `user_models` from every is_user_defined
row, then strips those model IDs from every row where is_aggregator(slug)
is True. But is_aggregator() returns True for *every* `custom:*` slug, and
list_authenticated_providers emits named custom providers with slug
`custom:<name>` and is_user_defined=True. So a user's own custom provider
is treated as an aggregator and filtered against user_models — which holds
exactly its own models (the row helped build that set). Every model is
removed, the row drops to zero, and the provider disappears from the model
picker.

Guard the dedup loop to skip is_user_defined rows: a user's configured
provider is never an aggregator duplicate of itself. Built-in aggregators
(openrouter, etc.) are still deduped as before. Adds a regression test.
2026-06-16 13:04:07 -07:00
Jaaneek
f4ef70f6fc docs(xai): update default model references to grok-build-0.1
Reflect the default-model change in the xAI Grok OAuth guide, the web
search docs (EN + zh-Hans), and the web provider docstring. grok-4.3 is
kept in the model tables as the previous default; the Nous/OpenRouter
aggregator catalog still lists grok-4.3 and is left unchanged.
2026-06-16 11:50:17 -07:00
Jaaneek
bbc842d31e feat(xai): default to grok-build-0.1
Switch the default model for the xAI/Grok provider and the xAI web
search backend from grok-4.3 to grok-build-0.1. grok-build-0.1 is
already recognized by the model metadata, so no new model definition
is required; grok-4.3 remains selectable.
2026-06-16 11:50:17 -07:00
teknium
28f92478e3 test(hooks): cover session:compress event; drop dead import
Follow-up to salvaged PR #41624:
- Remove stray urllib.parse import in run_agent.py (cherry-pick cruft, unused)
- Add tests: session:compress emits with correct context, no-callback is
  safe, and a callback exception does not break compression
2026-06-16 11:45:36 -07:00
Wolfram Ravenwolf
e76e7b5073 feat(hooks): session:compress event_callback for MemPalace sync 2026-06-16 11:45:36 -07:00
kshitij
8fa562a399
Merge pull request #47391 from kshitijk4poor/feat/add-glm-5.2
feat: add z-ai/glm-5.2 to OpenRouter and Nous model lists
2026-06-17 00:02:05 +05:30
brooklyn!
44e5848e74
feat(desktop): stream subagent activity into watch windows (#47060)
* feat(desktop): stream subagent replies into watch windows

A desktop watch window resumes a child session lazily (no full agent) and
mirrors the parent-relayed `subagent.*` events into native child-session
stream events. The child's streamed reply text was never relayed, so the
window sat blank while the subagent "talked".

- delegate_tool: forward the child's `run_conversation` stream tokens up the
  progress relay as `subagent.text` (inert under CLI/TUI — their progress
  handlers ignore non-tool event types; only a gateway watch window mirrors it).
- server: mirror `subagent.text` -> `message.delta` on the child sid only, and
  skip the parent emit (per-token frames are meaningless on the parent session,
  which shows the child via the spawn tree). Demote `subagent.start` to a
  one-time goal header and drop the noisy `subagent.progress` mirror — tools
  already mirror natively.
- server: guard `_start_agent_build` so a lazy watch session spectating an
  in-flight child stays lazy; incidental RPCs were upgrading it to a full
  agent mid-stream and silently killing the mirror.

* fix(desktop): keep watch-window chat clear of titlebar chrome

Secondary windows (new-session scratch, subagent watch, cmd-click pop-out)
hide the titlebar tool cluster + session header, so the transcript ran to the
window's top edge and streamed text slid up under the OS traffic lights.

- Gate the hidden chrome on `isSecondaryWindow()` everywhere (app-shell,
  chat header, thread list) instead of the narrower new-session flag.
- Add a fixed opaque drag-strip at the top of the secondary-window transcript:
  content padding alone scrolls away with the text, so the strip masks
  anything behind it and keeps the window draggable like the main header.

* fix: WSL subagent window

* fix: subagent window top padding

---------

Co-authored-by: Austin Pickett <pickett.austin@gmail.com>
Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
2026-06-16 14:30:11 -04:00
teknium
6ebc449915 fix(prompt): isolate truncation warnings per context
Follow-up to salvaged PR #41619: replace the module-global
_truncation_warnings list with a contextvars.ContextVar so concurrent
gateway-session prompt builds can't drain or clear each other's pending
warnings (cross-session leak). Adds a context-isolation test.
2026-06-16 11:28:35 -07:00
Wolfram Ravenwolf
f6a42b1acf feat(prompt): make context-file truncation limit configurable
PROBLEM: Automatic context files such as SOUL.md and AGENTS.md were capped by a hardcoded CONTEXT_FILE_MAX_CHARS value. Amy's local fork had raised that constant from 20K to 25K so a larger SOUL.md would not be silently truncated, but the hardcoded 25K value changed upstream default behavior and made the patch less generally useful.

SOLUTION: Restore the upstream-compatible 20K default, add a context_file_max_chars config setting for users who intentionally keep larger identity/project-context files, keep chat-visible truncation warnings, and document the new setting. Tests cover the default, config override, explicit max_chars precedence, and the warning text.
2026-06-16 11:28:35 -07:00
kshitijk4poor
b2da39a0f3 feat: add z-ai/glm-5.2 to OpenRouter and Nous model lists
Z.ai released GLM 5.2 on 2026-06-15, available on OpenRouter:
  - https://openrouter.ai/z-ai/glm-5.2

GLM-5.2 is Z.ai's flagship for long-horizon tasks, shipping a 1M-token
context window (up from 200K on GLM 5.1) and tool calling. Per the
OpenRouter API: text-only, context_length 1048576, tools supported.
No separate -fast variant exists.

The 1M context length, native zai picker entry, setup wizard, and Z.ai
coding-plan auth entries for glm-5.2 already landed on main. This fills
the remaining gap: the two aggregator surfaces where glm-5.1 appears but
glm-5.2 did not.

Changes:

  hermes_cli/models.py
    - Add z-ai/glm-5.2 to the OpenRouter fallback snapshot (OPENROUTER_MODELS)
      and the Nous Portal curated list (_PROVIDER_MODELS["nous"]), newest
      flagship first. Live catalogs surface it automatically when reachable;
      the fallback lists matter when the manifest fetch fails.

  website/static/api/model-catalog.json
    - Regenerated via scripts/build_model_catalog.py (not hand-edited) so the
      manifest stays in sync with the source lists; guarded by
      tests/hermes_cli/test_model_catalog.py.
2026-06-16 23:35:45 +05:30
kshitij
17251e865b
Merge pull request #46857 from liuhao1024/fix/model-picker-merge-live-static
fix(models): merge live API results with curated static catalog in generic provider path
2026-06-16 23:30:34 +05:30
kshitijk4poor
658ac1d866 fix(models): keep curated-first ordering in live+curated merge; use pure-catalog helper in validation
The generic live+curated merge (commit 630b438) seeded the merged list
from live results, demoting curated-only models below live ones. That
regressed #46309, which deliberately surfaces the newest curated model
(kimi-k2.7-code) FIRST in the native picker even when the live /models
listing lags. Restore curated-first ordering: curated entries lead (in
catalog order), live-only entries are appended for discovery. This keeps
the #46850 fix (zai glm-5.2 now appears) without the kimi regression.

Also switch the validate_requested_model curated fallback (commit
ee7b8a4) from provider_model_ids() — which triggers a second, uncached
live /models fetch with its own 8s timeout and may resolve different
credentials than the api_key/base_url just probed — to the pure-catalog
helper _model_in_provider_catalog(). Membership is checked against the
shipped catalog only, with no extra network call.

Tests: restore the curated-first assertion in
test_kimi_coding_live_catalog_does_not_hide_curated_k2_7_code; update
the new merge tests to curated-first semantics; de-circularize the
validation fallback tests to patch _PROVIDER_MODELS (the real source)
instead of mocking the function under test.
2026-06-16 23:25:07 +05:30
Teknium
c2c55c4443 fix(memory): strip skill scaffolding for all providers, not just openviking
Generalizes #32663 (@ehz0ah). The slash-skill scaffolding pollution
affected every auto-syncing memory provider — mem0, hindsight, retaindb,
byterover, honcho, supermemory all store/embed the raw user turn, so a
/skill invocation poisoned their stores with the full skill body, not just
openviking.

- Lift the contributor's parser into agent/skill_commands.py as the canonical
  extract_user_instruction_from_skill_message(), co-located with the message
  builders so the markers can't drift.
- Strip once in MemoryManager.{prefetch_all,queue_prefetch_all,sync_all} —
  fixes the whole provider fan-out, bare /skill turns are skipped entirely.
- OpenViking's _derive_openviking_user_text() now delegates to the shared
  helper as defense-in-depth (no duplicated marker literals).
- Marker-drift regression now asserts against the canonical skill_commands
  constants; add manager-level coverage proving every provider gets clean text.
2026-06-16 10:37:37 -07:00
Hao Zhe
e3adbb5ae9 fix(openviking): sanitize skill memory input 2026-06-16 10:37:37 -07:00
teknium1
e236bb87eb docs(skills): regenerate shop skill page after shop-app rename 2026-06-16 10:37:21 -07:00
teknium1
cf52370253 chore(release): AUTHOR_MAP entry for Joe Rinaldi Johnson 2026-06-16 10:37:21 -07:00
teknium1
d7668aaff5 chore(skills/shop): tighten description to ≤60 chars, credit contributor 2026-06-16 10:37:21 -07:00
Joe Rinaldi Johnson
5094325140 feat(skills): replace shop-app with CLI-based shop skill (v1.0.1)
Rewrites the Shop personal-shopping-assistant skill to use the
@shopify/shop-cli (with a full direct-API fallback in references/),
replacing the previous curl-only shop-app skill.

- Rename optional-skills/productivity/shop-app -> shop
- Add references/: catalog-mcp.md, direct-api.md, safety.md, legal.md
- Catalog discovery via Shopify Global Catalog MCP (search / lookup /
  get-product), device-authorization sign-in, UCP agent checkout with
  delegated spending budget, and order tracking / returns / reorder
- One-product-per-message presentation rules + per-channel overrides
- Expanded security, safety, and legal guidance

Website docs are auto-generated from SKILL.md by CI
(website/scripts/generate-skill-docs.py), so no docs are hand-edited here.
2026-06-16 10:37:21 -07:00
brooklyn!
c6e99ab375
Merge pull request #46959 from NousResearch/bb/composer-model-selector
feat(desktop): composer model selector, per-model presets & external-provider disconnect
2026-06-16 09:55:57 -05:00
Brooklyn Nicholson
80e4b8985e feat(desktop): tighten composer model picker interactions
Clicking a model row in the composer dropdown now commits and closes the menu
(via a close context); the hover-revealed reasoning/fast submenu stays open to
tweak. The pill shows a quiet braille loader instead of literal "No model"
until one resolves, and steer takes over the mic slot while typing into a
running agent.
2026-06-16 09:50:27 -05:00
Brooklyn Nicholson
7d938cc5c9 fix(desktop): keep live model switch metadata truthful
A live config.set model switch already moved the next API call to the new model,
but the conversation could still restore an old sessions.system_prompt snapshot
whose Model/Provider lines named the previous runtime. That made "what model are
you?" answer from stale metadata even while inference ran on the new model.

After a live switch we now refresh the stored system prompt and append a real
system-history pivot (not a fake user turn) so the transcript itself records the
new model/provider. Restore also rejects already-stale prompt snapshots when
their Model/Provider lines disagree with the runtime, so existing bad sessions
self-heal.
2026-06-16 09:50:17 -05:00
Brooklyn Nicholson
cb6b4127e7 refactor(desktop): make composer model picker sticky session state
The picker no longer touches the profile default. Model/effort/fast live as
plain UI state persisted in localStorage, so a pick follows across Cmd+N and
restarts instead of snapping back. New chats ship that state through
session.create as per-session overrides; live chats still scope switches to the
current session. Settings -> Model remains the only surface that writes the
profile default.

The gateway now accepts those session.create overrides, builds the agent with
them directly, reflects them in the immediate session.info payload, and writes
the chat's own model_config into the lazy DB row so reconnect/resume restores
that chat instead of the global default.
2026-06-16 09:50:07 -05:00
Teknium
a68ac0c49a
feat(desktop): allow /browser connect on a local gateway (#47245)
* fix(skills): guard recursive skill delete against tree-escape

Port from Kilo-Org/kilocode#11240. Their issue #11227 lost a user's entire
working directory: a built-in-skill sentinel location resolved to the server
cwd and the skill-removal endpoint ran a recursive delete on it.

Hermes' /skills uninstall path (skills_hub.py) is already hardened, but the
agent-facing skill_manage(action='delete') path did a bare
shutil.rmtree(skill_dir) with no last-line validation. Add _validate_delete_target():
refuse to rmtree a path that (1) isn't strictly inside a known skills root,
(2) is a skills root itself, or (3) is reached via a symlink/junction.

Tests: 4 cases (normal delete works; symlinked dir, skills-root, out-of-tree
all refused). E2E verified with real symlink + file I/O.

* feat(desktop): allow /browser connect on a local gateway

/browser was hardcoded as terminal-only in the desktop slash palette, so
the chat GUI rejected it with "only available in the terminal interface."
The TUI already drives the live CDP connection via the browser.manage RPC.

Wire the same RPC into the desktop dispatcher as a /browser action handler,
gated to local-gateway connections ($connection.mode !== 'remote'). connect
mutates BROWSER_CDP_URL (and may launch Chrome) in the gateway process, so
it's only meaningful when that process runs on this machine; a remote
gateway gets a clear "local gateway only" message instead.
2026-06-16 09:03:43 -05:00
Wolfram Ravenwolf
16fc717091 fix(mattermost): harden delivery hygiene
PROBLEM: Mattermost threads can become invalid or enormous, exposing two failure modes: internal scratch/reasoning/commentary displays could leak into persistent Mattermost threads via global display toggles, while rejected threaded user-visible replies could disappear unless every failed send fell back flat. A broad flat fallback would pollute channels with tool/status/progress noise.

SOLUTION: Require explicit Mattermost platform opt-in for scratch displays, keep using the existing notify=True metadata marker for user-visible final text/media/file replies, and allow the Mattermost plugin adapter to flat-fallback only notify-worthy sends whose threaded POST failure looks like a broken root/thread. Keep tool/status/progress and other non-notify sends thread-strict. Add regression tests for display opt-in, notify-only broken-thread fallback, generic API failure suppression, and stream notify metadata.

Verification: tests/gateway/test_mattermost.py tests/gateway/test_stream_consumer.py tests/gateway/test_stream_consumer_thread_routing.py tests/gateway/test_stream_consumer_fresh_final.py tests/gateway/test_stream_consumer_draft.py; tests/gateway/test_session_api.py tests/gateway/test_status_command.py tests/gateway/test_resume_command.py tests/hermes_cli/test_commands.py; py_compile touched gateway files; git diff --check.

Session: Mattermost thread 6qg8e9dd1pd9pkhi74xyaa1mry, 2026-06-01.
2026-06-16 06:34:54 -07:00
teknium1
925b0d1ab5 chore: add zimigit2020 to release AUTHOR_MAP 2026-06-16 06:23:53 -07:00
Rory Evans
e65d74bc6f fix(gateway): accept metadata kwarg in WhatsApp/email send_image
`BasePlatformAdapter.send_multiple_images` passes `metadata=metadata` to
`send_image` / `send_image_file` / `send_animation` on every send. The
WhatsApp and email `send_image` overrides stopped their signature at
`reply_to`, so any image delivered as a URL (the common case — image-gen
backends return URLs) raised:

    TypeError: send_image() got an unexpected keyword argument "metadata"

and the image silently failed to send. Their sibling overrides
(`send_image_file` / `send_video` / `send_voice` / `send_document`)
already absorb it via **kwargs, which is why only plain image-URL sends
broke.

- whatsapp/email `send_image`: accept `metadata` (matches the base
  signature); WhatsApp forwards it to the super() text fallback.
- Add `tests/gateway/test_media_metadata_contract.py`: asserts WhatsApp +
  email accept it, plus a best-effort sweep over every adapter so the next
  slip fails at test time instead of in production.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 06:23:53 -07:00
Teknium
4858942c55
fix(auxiliary): honor main fallback chain for auto tasks (#47235) 2026-06-16 06:23:24 -07:00
Teknium
4d470b3dbb
fix(slack): route /debug via /hermes to restore Telegram-parity (#47248)
Slack caps apps at 50 slash commands and the registry is at that ceiling, so
adding /debug clamped it out of the native list and broke the telegram-parity
test (debug on Telegram, absent from Slack native slashes, in neither
exclusion set). Add 'debug' to _SLACK_VIA_HERMES_ONLY — same treatment credits
already gets. /debug stays native on CLI/TUI/Telegram/Discord and reachable via
/hermes debug on Slack.
2026-06-16 06:20:01 -07:00
Teknium
2483200963
test(tui): isolate session-create no-race test from shard-sibling leakage (#47230)
test_session_create_no_race_keeps_worker_alive flaked on CI shard 3 with
'build thread unregistered its own notify despite no race' while passing
20/20 in isolation locally. Root cause: daemon build threads from sibling
session.create tests in the same shard process mutate the shared
server._sessions dict under _sessions_lock and can replace/pop entries
mid-run, flipping this build thread's 'replaced' check (server.py:1011) to
True and triggering a spurious unregister_gateway_notify.

Fix is test-only: snapshot + clear server._sessions before the request so
the test sees only its own session, restore siblings in finally. Also assert
agent_ready.wait() actually returned True (was silently ignoring timeout) and
bump the timeout 2s -> 10s for loaded CI runners.
2026-06-16 05:56:50 -07:00
teknium1
1ac76a9472 chore: add MrDiamondBallz to release AUTHOR_MAP 2026-06-16 05:56:11 -07:00
MrDiamondBallz
9a59ad73dd fix(auth): preserve Codex pool-only rate-limit state
Classify exhausted pool-only openai-codex credentials as quota/rate-limited instead of missing auth. This prevents auth status and runtime credential resolution from reporting missing credentials when a valid manual:device_code pool credential exists but is temporarily in a 429 usage-limit cooldown.

Adds regression coverage for pool-only Codex auth status and runtime resolution.
2026-06-16 05:56:11 -07:00
teknium
6373aba80f feat(gateway): rename to tool_progress_grouping, add config/docs/tests
Follow-up to salvaged PR #41620:
- Rename tool_progress_style -> tool_progress_grouping (clearer intent)
- Add display.tool_progress_grouping to DEFAULT_CONFIG (accumulate default)
- Document in messaging docs incl. 'separate is noisier, only where progress enabled'
- Add resolver tests (default/global/override/invalid/case)
2026-06-16 05:49:24 -07:00
Wolfram Ravenwolf
fc956b9db6 feat: add tool_progress_style config (accumulate vs separate)
Add display.tool_progress_style setting to control how tool progress
messages are displayed in chat platforms:

- 'accumulate' (default): Edit a single message with all tool calls
  (new v0.9.0 behavior)
- 'separate': Send each tool call as its own message, interleaved
  with thinking messages (pre-v0.9 behavior, better readability)

The setting participates in the per-platform display override system
and can be set globally or per-platform.

Files: gateway/display_config.py, gateway/run.py
2026-06-16 05:49:24 -07:00