Commit graph

17164 commits

Author SHA1 Message Date
brooklyn!
61e7db2820
Merge pull request #69887 from NousResearch/bb/tile-resume-profile
fix(desktop): route tiled session resumes to the owning profile
2026-07-23 00:28:01 -05:00
ethernet
26f1f6a76b
feat(desktop): improve tool call detail views (#69868)
* fix(desktop): improve fallback tool-call details

Show failed image-generation calls through the normal fallback row, remove duplicate normal-mode web-search JSON, and format Technical Mode payloads as readable JSON.

* feat(desktop): render terminal tool calls as transcripts

Show terminal commands with a prompt and exit status, then reveal ANSI-safe stdout and stderr in the expanded tool row.

* fix(desktop): reconcile tool calls by command

Match context-only tool starts with command-bearing completions when their IDs differ, preventing stale duplicate terminal rows. Show the web-search query above its result cards.
2026-07-23 01:27:38 -04:00
Teknium
c1b0f6f3c1
feat(kanban): per-task model dropdown — set/override worker model+provider from the board (#69876)
Adds the missing write path for the per-task model_override column (which
was previously only settable via manual SQL) and pairs it with a
provider_override so cross-provider switches resolve correctly:

- kanban_db: provider_override column (+migration), set_model_override()
  with model_override_set event, create_task(model_override=,
  provider_override=), dispatcher spawns worker with -m <model>
  [--provider <name>]
- dashboard: Model row in the task drawer — dropdown fed by a new
  /model-options endpoint (build_models_payload substrate, provider-grouped,
  free-text fallback), PATCH + bulk model override support
- CLI: kanban create --model/--provider, new kanban set-model subcommand,
  show prints the provider
- agent tools: kanban_create accepts model/provider; show/list expose
  provider_override

Rate-limit recovery flow: override is settable on running tasks and takes
effect on the next dispatch, without touching the worker profile's config.
2026-07-22 22:23:24 -07:00
Brooklyn Nicholson
c3602f7f05 fix(desktop): route tiled session resumes to the owning profile
`resumeTile` — the cold-resume path for a session opened in a tile / split
pane — resumed with `{ session_id, cols }` and read messages with no profile,
so a tile opening a session from another profile let the gateway fall back to
the launch-profile DB and fork the conversation into the wrong profile: the
same cross-profile bleed the recovery resumes had (#67603), just a sibling
call path. Resolve the owning profile via the shared `resolveSessionProfile`
and carry it on both the transcript prefetch and the resume RPC.

Co-authored-by: oliviaaaa7788 <oliviaaaa7788@users.noreply.github.com>
2026-07-23 00:18:35 -05:00
Gille
7cbdcf1ba6
fix(desktop): render remote markdown images in chat (#57944)
* fix(desktop): render remote markdown images in chat

* test(desktop): cover remote markdown image resolution
2026-07-23 00:16:20 -05:00
hermes-seaeye[bot]
ca16a8e07d
fmt(js): npm run fix on merge (#69879)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-23 05:14:26 +00:00
brooklyn!
e59dcf46f1
fix(desktop): end the #67603 model-switch dup, cross-profile session bleed, and [System:] bubble (#69861)
* fix(desktop): stop model-switch dup + route recovery resumes to the owning profile

Fixes two Desktop session-reconciliation symptoms from #67603.

Symptom 1 — duplicated user bubble after a model switch. The gateway
persists model-switch / personality notices as role=user `[System: …]`
rows (tui_gateway/server.py) so strict OpenAI-compatible providers don't
reject a non-leading system message (#48338). `preserveLocalPendingTurnMessages`
paired local optimistic rows with the stored transcript by user-role
ordinal, so a marker between two real user turns shifted every later
ordinal and the optimistic row was re-appended at the bottom. The single
trailing-marker case is already covered by the compression-era
`latestAuthoritativeUser` guard, but two switches around one turn (marker
before AND after the committed prompt) still duplicated it. Exclude
`[System:` bookkeeping markers from ordinal pairing on both sides.

Symptom 2 — a session appearing under two profiles. The main resume path
already resolves a session's owning profile via `resolveStoredSession`
(cache → active backend → cross-profile probe), but the recovery
`session.resume` calls (stale runtime id, session-not-found, wedged loop,
redirect) omitted `profile`, so the gateway fell back to the launch-profile
DB and forked the conversation into the wrong profile. Route every recovery
resume — and an uncached right-click branch — through the same resolver so
the profile is carried even for sessions outside the paginated sidebar
window (the cache-miss gap).

Tests: discriminating two-switch marker test (fails before, passes after);
cache-hit + cross-profile cache-miss coverage for the recovery resume and
for branching an uncached session.

Supersedes #68665 and #63590.
Closes #67603.

Co-authored-by: Dolverin <5910064+Dolverin@users.noreply.github.com>
Co-authored-by: oliviaaaa7788 <274182427+oliviaaaa7788@users.noreply.github.com>

* fix(desktop): scope the remembered session id per profile

A single global `hermes.desktop.lastSessionId` key remembered ONE session
across every profile, so a relaunch or cold start under profile B would try
to restore a session owned by profile A — reinforcing the impression that a
conversation had bled between profiles (#67603, second symptom).

Key the remembered id by the session's owning profile (resolved from the
session row's `profile`, falling back to the active gateway profile), read it
back for the active profile on restore, and clear an exhausted session under
its owner. The default profile keeps the original unsuffixed key so existing
installs' remembered session survives the upgrade.

Co-authored-by: oliviaaaa7788 <oliviaaaa7788@users.noreply.github.com>

* fix(gateway): hide [System:] bookkeeping markers from every transcript

Model-switch and personality notices are persisted as role=user `[System: …]`
rows so strict providers accept them mid-history, but they are model-facing
runtime metadata, not user turns. `_history_to_messages` — the single display
projection every client reads — passed them straight through, so on resume or
reload they rendered as a fake user bubble in the desktop, TUI, CLI, and web
transcripts.

Drop them in that projection. The raw marker stays in `session["history"]`
for the model, so nothing changes for inference; only the display loses a row
that never belonged to the user. This also removes the stored marker from the
payload the desktop reconciles against, killing the ordinal shift that
duplicated the optimistic prompt (#67603) at its source — the desktop-side
marker exclusion remains as a fallback for older backends.

Co-authored-by: Dolverin <Dolverin@users.noreply.github.com>

---------

Co-authored-by: Dolverin <5910064+Dolverin@users.noreply.github.com>
Co-authored-by: oliviaaaa7788 <274182427+oliviaaaa7788@users.noreply.github.com>
Co-authored-by: oliviaaaa7788 <oliviaaaa7788@users.noreply.github.com>
Co-authored-by: Dolverin <Dolverin@users.noreply.github.com>
2026-07-23 00:05:25 -05:00
brooklyn!
e1367b44bc
fix(desktop): let clarify choices and overlays keep their keys from type-to-focus (#69869) 2026-07-23 00:04:12 -05:00
Gille
8e01309917
fix(tui_gateway): preserve websocket batch order (#69684)
* fix: serialize TUI gateway websocket sends

* fix(tui_gateway): preserve websocket batch order

* refactor(tui_gateway): drop unused _safe_send wrapper

The batch-serialization fix routes every send through _safe_send_many;
_safe_send became a dead single-line wrapper with no callers. Remove it.

---------

Co-authored-by: supplefrog <78985073+supplefrog@users.noreply.github.com>
Co-authored-by: Brooklyn Nicholson <brooklyn.bb.nicholson@gmail.com>
2026-07-23 05:02:45 +00:00
Teknium
b0358cf3c8 fix(slack): unify DM-resolution caches and bound wave-2 caches (C16 policy)
Post-rebase consolidation over the merged C7/C16/C10 work:
- _ensure_dm_conversation now records workspace ownership via
  _remember_channel_team and bounds _dm_conversation_cache (cap 5000)
- module-level _slack_dm_cache (C7 standalone path) bounded oldest-first
- _user_is_bot_cache (C10) bounded with _trim_oldest_dict_entries
- caption-mode contract tests updated for the C7+C8 merged media path
2026-07-22 21:58:13 -07:00
Teknium
527c68baa4 chore(contributors): add email mapping for slack media salvage (robzolkos) 2026-07-22 21:58:13 -07:00
Teknium
994a405f2d fix(slack): resolve bare user targets to DMs across all adapter send paths
Widen the #19237 send_message fix to the live adapter: send, _upload_file,
send_multiple_images, send_image, send_video, send_document,
send_exec_approval, send_slash_confirm, and send_clarify now route bare
Slack user IDs (U.../W...) through a shared _ensure_dm_conversation helper
before calling chat.postMessage / files_upload_v2, which reject user IDs.

This closes the gap in #17261 where an attachment worked when replying in
a thread but failed when directed at a user DM, and extends the DM-open
fallback to clarify/approval Block Kit prompts so gated actions can reach
a user directly.

Resolution uses the workspace-scoped client (multi-workspace installs open
the DM with the right bot token), caches per (team, user), and records the
opened D... channel in the channel→team map. On failure the original
target passes through so the downstream API call surfaces the real Slack
error.

Fixes #17261
Refs #19236
2026-07-22 21:58:13 -07:00
Teknium
4ab4894f44 fix(gateway): make post-stream media delivery explicit-only
The post-stream helper (_deliver_media_from_response) rescanned the
already-streamed response and promoted bare local filesystem paths into
real uploads via extract_local_files. Since the visible reply was already
streamed verbatim, any bare path there is either text the user has seen
or stale inspected/tool content — not an attachment request. On Slack this
uploaded images from stale inspected content after otherwise clean replies.

Post-stream delivery now honors only explicit MEDIA: directives. The
non-streaming path in gateway/platforms/base.py keeps its bare-path
auto-detect, because that path controls the visible text and strips the
path from the reply when it attaches — auto-attach is intentional there.

Regression tests: bare image/document paths in a streamed reply produce
no upload; explicit MEDIA: tags still deliver.

Fixes #20834
2026-07-22 21:58:13 -07:00
dsad
c83a196402 fix(slack): gate message files before metadata fetch 2026-07-22 21:58:13 -07:00
dsad
5bb933eedf fix(slack): reject unauthorized users before event construction
Add early auth check in _handle_slack_message() that runs BEFORE any
API calls (thread context fetch, user name resolution, file downloads)
or file processing. Unauthorized users could previously trigger Slack
API calls and file downloads before the runner's _is_user_authorized
gate rejected them.

Same pattern as Telegram fix #54164: build a SessionSource and check
the runner's _is_user_authorized at the adapter level before event
construction consumes resources.

Fixes the gap where Slack has no adapter-level auth gate while Discord
and Telegram have adapter-level allowlists.
2026-07-22 21:58:13 -07:00
dsad
f54e8706f7 fix(platforms): block image upload redirects to private URLs 2026-07-22 21:58:13 -07:00
Rob Zolkos
40351d0923 fix(slack): resolve file events with cached workspace team 2026-07-22 21:58:13 -07:00
davesecops
57f8ba3b19 fix(slack): open DMs for user send targets 2026-07-22 21:58:13 -07:00
HexLab98
8df8f86784 test(slack): cover send_message MEDIA delivery
Add standalone-sender media cases and route coverage; point the
non-media platform assertions at SMS now that Slack supports MEDIA.
2026-07-22 21:58:13 -07:00
HexLab98
5f4c952ab0 fix(slack): support MEDIA attachments in send_message
Slack could already deliver files in-channel via the gateway, but
send_message omitted MEDIA for Slack and told the model it was
unsupported — causing agents to inconsistently refuse PDF sends.
Wire Slack through files_upload_v2 in the standalone sender.
2026-07-22 21:58:13 -07:00
Brooklyn Nicholson
62bfba521d fix(gateway): hide [System:] bookkeeping markers from every transcript
Model-switch and personality notices are persisted as role=user `[System: …]`
rows so strict providers accept them mid-history, but they are model-facing
runtime metadata, not user turns. `_history_to_messages` — the single display
projection every client reads — passed them straight through, so on resume or
reload they rendered as a fake user bubble in the desktop, TUI, CLI, and web
transcripts.

Drop them in that projection. The raw marker stays in `session["history"]`
for the model, so nothing changes for inference; only the display loses a row
that never belonged to the user. This also removes the stored marker from the
payload the desktop reconciles against, killing the ordinal shift that
duplicated the optimistic prompt (#67603) at its source — the desktop-side
marker exclusion remains as a fallback for older backends.

Co-authored-by: Dolverin <Dolverin@users.noreply.github.com>
2026-07-22 23:54:05 -05:00
Brooklyn Nicholson
143942d497 fix(desktop): scope the remembered session id per profile
A single global `hermes.desktop.lastSessionId` key remembered ONE session
across every profile, so a relaunch or cold start under profile B would try
to restore a session owned by profile A — reinforcing the impression that a
conversation had bled between profiles (#67603, second symptom).

Key the remembered id by the session's owning profile (resolved from the
session row's `profile`, falling back to the active gateway profile), read it
back for the active profile on restore, and clear an exhausted session under
its owner. The default profile keeps the original unsuffixed key so existing
installs' remembered session survives the upgrade.

Co-authored-by: oliviaaaa7788 <oliviaaaa7788@users.noreply.github.com>
2026-07-22 23:53:59 -05:00
brooklyn!
6096f73ce8
feat(desktop): add keyboard navigation to clarify choices (#69799)
Co-authored-by: Mapurite <272619650+mapu-og@users.noreply.github.com>
2026-07-23 04:48:32 +00:00
Brooklyn Nicholson
5fccc9aae9 fix(desktop): stop model-switch dup + route recovery resumes to the owning profile
Fixes two Desktop session-reconciliation symptoms from #67603.

Symptom 1 — duplicated user bubble after a model switch. The gateway
persists model-switch / personality notices as role=user `[System: …]`
rows (tui_gateway/server.py) so strict OpenAI-compatible providers don't
reject a non-leading system message (#48338). `preserveLocalPendingTurnMessages`
paired local optimistic rows with the stored transcript by user-role
ordinal, so a marker between two real user turns shifted every later
ordinal and the optimistic row was re-appended at the bottom. The single
trailing-marker case is already covered by the compression-era
`latestAuthoritativeUser` guard, but two switches around one turn (marker
before AND after the committed prompt) still duplicated it. Exclude
`[System:` bookkeeping markers from ordinal pairing on both sides.

Symptom 2 — a session appearing under two profiles. The main resume path
already resolves a session's owning profile via `resolveStoredSession`
(cache → active backend → cross-profile probe), but the recovery
`session.resume` calls (stale runtime id, session-not-found, wedged loop,
redirect) omitted `profile`, so the gateway fell back to the launch-profile
DB and forked the conversation into the wrong profile. Route every recovery
resume — and an uncached right-click branch — through the same resolver so
the profile is carried even for sessions outside the paginated sidebar
window (the cache-miss gap).

Tests: discriminating two-switch marker test (fails before, passes after);
cache-hit + cross-profile cache-miss coverage for the recovery resume and
for branching an uncached session.

Supersedes #68665 and #63590.
Closes #67603.

Co-authored-by: Dolverin <5910064+Dolverin@users.noreply.github.com>
Co-authored-by: oliviaaaa7788 <274182427+oliviaaaa7788@users.noreply.github.com>
2026-07-22 23:42:43 -05:00
hermes-seaeye[bot]
328e4f5a1e
fmt(js): npm run fix on merge (#69852)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-23 04:40:24 +00:00
brooklyn!
dc861964fc
fix(desktop): clarify options defensive rendering and layout fix (#69796)
- Replace undefined  CSS class with Tailwind v4's
   so long choice text wraps properly
  instead of overflowing the button container.
- Add  validation that strips non-string items,
  blanks, newlines, and text >200 chars — preventing garbage/JSON
  arrays from rendering as raw values.
- Add diagnostic  logging at both the gateway event
  handler and the tool-args parser when choices are dropped, so
  malformed payloads are no longer silent.
- Apply  in both the gateway event handler
  () and the inline tool-args parser
  () for consistent defense in depth.
- Add unit tests for  covering null, non-array,
  mixed-type, blank, multiline, and overlong inputs.

Closes #69122

Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>
2026-07-23 04:32:33 +00:00
brooklyn!
46fb10203b
Merge pull request #69841 from NousResearch/bb/salvage-69428-expandable-overlay
fix(desktop): free code-block scrollbar & last-line selection from the toggle overlay
2026-07-22 23:29:34 -05:00
brooklyn!
d21165c2f0
fix(desktop): keep clarify answerable across reconnect/hydration + tool-progress off (#69795)
* fix(desktop): keep clarify lifecycle when tool progress is off

* fix(desktop): render clarify prompt from the request event

Re-authored onto the current use-message-stream/gateway-event.ts (the
original patched the pre-split use-message-stream.ts). When the tool.start
row that normally mounts the inline clarify UI is missed (stream reconnect
/ hydration race), upsert a stable pending clarify tool row from
clarify.request itself so the prompt stays answerable; a real
tool.start/complete with the same request id merges rather than duplicates.

Co-authored-by: 정수환 <centerid@naver.com>

* chore(contributors): map centerid@naver.com -> lidises

Attribution mapping for the salvaged #47544 commit.

* fix(desktop): correlate clarify rows by question so hydration can't duplicate

The hydrated row (from clarify.request's request_id) and the real tool.start
row (the model's tool_call_id) have different ids, so id-only matching appended
a second clarify card in the normal path (caught by the BLOCKING_CLARIFY e2e:
'question' resolved to 2 elements). Add 'question' to the tool match-value keys
so a clarify upsert merges into the existing pending clarify row regardless of
id (same request<->args correlation ClarifyToolPending already uses); when no
row exists yet (reconnect/hydration) it still creates one.

---------

Co-authored-by: 정수환 <centerid@naver.com>
2026-07-22 23:25:48 -05:00
brooklyn!
7d28e84e72
fix(gateway): deliver assistant prose before the clarify poll (#69775)
* fix(gateway): deliver assistant prose before clarify poll

The clarify poll is sent on a separate, agent-thread-blocking path while
buffered assistant prose (interim commentary / streamed deltas) sits in
the GatewayStreamConsumer queue, drained asynchronously. The poll won the
race, so the question rendered ABOVE its own explanation, and a redundant
'clarify: ...' tool-progress bubble wedged between them.

- Add GatewayStreamConsumer.flush_pending_sync(): a synchronous flush
  barrier (_FLUSH sentinel + threading.Event) that blocks the agent
  thread until everything queued before it is finalized and delivered.
- Call it in the gateway clarify callback before send_clarify, so prose
  always lands before the poll. Best-effort with a 3s timeout.
- Suppress the redundant clarify tool-progress bubble (the poll already
  shows the question + options).

Tests: 3 new ordering/timeout cases in test_stream_consumer.py.
(cherry picked from commit 9a6e27badb)

* chore(contributors): map matvey.sakhnenko03@icloud.com -> sakhnenkoff

Attribution mapping for the salvaged #54328 commit (Cluster C).

---------

Co-authored-by: Matvii Sakhnenko <matvey.sakhnenko03@icloud.com>
2026-07-22 23:25:35 -05:00
brooklyn!
507d479c8c
fix(clarify): one canonical timeout across CLI, TUI/desktop, and gateway (#69774)
* test(clarify-gateway): cover signature, timeout fallback, and notify paths for 100% coverage

Fixes #36531

(cherry picked from commit 5265dfe2f5)

* fix(clarify): one canonical timeout across CLI, TUI/desktop, and gateway

The clarify wait timeout was resolved three different (wrong) ways:

- CLI (`cli.py`, `hermes_cli/callbacks.py`) read a non-existent top-level
  `clarify.timeout`, so it always fell through to a hardcoded 120s instead of
  the canonical `agent.clarify_timeout` (default 3600) the gateway uses (#42969).
- The TUI/desktop bridge called `_block("clarify.request", …)` with no timeout,
  so it used the hardcoded 300s `_block` default and ignored config (#51960).
- There was no way to disable the auto-skip: a user who wanted the agent to wait
  indefinitely while they think couldn't get it.

Collapse all of this onto a single resolver:

- `tools.clarify_gateway.resolve_clarify_timeout(config)` is the one source of
  truth. Order: explicit legacy `clarify.timeout` (back-compat) → canonical
  `agent.clarify_timeout` → 3600. `<= 0` is preserved verbatim as "unlimited".
- CLI, callbacks, and the TUI bridge (`_clarify_timeout_seconds`) all route
  through it, so the three surfaces can't drift.
- `<= 0` means unlimited everywhere: `wait_for_response` and `_block` drop the
  deadline (heartbeat still fires), and the CLI hides its countdown.

Tests: resolver order / default / non-numeric / unlimited-sentinel; an
unlimited `wait_for_response` blocks until resolved rather than auto-skipping;
the TUI clarify bridge passes the configured timeout to `_block`.

Supersedes #42974 (CLI key), #51993 (TUI honors config), and #68986 (unlimited
wait); folds in #52031 (clarify_gateway coverage).

Co-authored-by: liuhao1024 <liuhao1024@users.noreply.github.com>
Co-authored-by: lkevincc0 <lkevincc0@users.noreply.github.com>
Co-authored-by: theone139344 <theone139344@users.noreply.github.com>
Co-authored-by: baauzi <baauzi@users.noreply.github.com>

---------

Co-authored-by: Christopher-Schulze <210261288+Christopher-Schulze@users.noreply.github.com>
Co-authored-by: liuhao1024 <liuhao1024@users.noreply.github.com>
Co-authored-by: lkevincc0 <lkevincc0@users.noreply.github.com>
Co-authored-by: theone139344 <theone139344@users.noreply.github.com>
Co-authored-by: baauzi <baauzi@users.noreply.github.com>
2026-07-22 23:25:13 -05:00
brooklyn!
3dd9d5e692
fix(tui_gateway): tolerate late clarify + terminal.read replies after timeout (#69773)
`_block()` bridges four blocking request types — secret, sudo, clarify,
terminal.read — with an identical lifecycle: on timeout the tool gives up and
returns empty, but a slow renderer (or a WebSocket reconnect that dropped
tool.complete) can still answer afterward. Only secret and sudo tolerated that
late reply; clarify and terminal.read still hit the generic 4009 "no pending
request" error, which clients surface as a raw JSON-RPC string (and at least
one desktop fork re-armed the pending request on the error).

Bring the two stragglers in line with the pair that already works:
- `_block` now emits `{event}.expire` on timeout for all four request types.
- `clarify.respond` and `terminal.read.respond` pass `allow_expired=True`, so a
  late answer resolves to `{"status": "expired"}` instead of erroring.

Tests parametrize the timeout-expiry and late-idempotent-response cases over
all four bridges; the old test asserting clarify stays a 4009 error is updated
to the new graceful contract.

Supersedes #56571 (clarify) and #64886 (terminal.read) — same root cause, one fix.

Co-authored-by: liuhao1024 <liuhao1024@users.noreply.github.com>
Co-authored-by: pierrenode <pierrenode@users.noreply.github.com>
2026-07-22 23:24:42 -05:00
Brooklyn Nicholson
936b407a72 fix(desktop): free code-block scrollbar & last-line selection from the toggle overlay
`ExpandableBlock` used a single full-width `absolute inset-x-0 bottom-0`
button that was BOTH the fade overflow cue and the expand/collapse control.
It sat on top of the whole 28px bottom strip, so it swallowed:

- the horizontal scrollbar of a wide code block (couldn't scroll sideways), and
- pointer/drag events on the block's last line (couldn't select/copy the tail).

Split the two responsibilities: the full-width fade stays as a pure
`pointer-events-none` cue, and the only clickable target is a compact toggle
pinned to the right edge (`pointer-events-auto`), clear of the draggable
scrollbar track. The inner container gains `overflow-x-auto` so wide code
gets a working scrollbar. Applies uniformly to code cards and the plain-text
fallback — no per-call prop needed.

Regression test asserts the pointer-events contract (fade `pointer-events-none`
+ full-width, toggle `pointer-events-auto` + right-pinned, not `inset-x-0`) and
that the toggle still flips `aria-expanded`.

Supersedes #69558, #69428
Fixes #69168

Co-authored-by: alelpoan <alelpoan@proton.me>
Co-authored-by: BerneYue <xiongyue_hnu@163.com>
2026-07-22 23:23:39 -05:00
Gille
390b03c455 fix(cli): persist provider on global model switch 2026-07-22 21:18:50 -07:00
Gille
b3ff5fc5b1 fix(vision): resolve namespaced custom provider overrides 2026-07-22 21:18:45 -07:00
HexLab98
125fac7943 test(cli): cover custom provider false shrink warning 2026-07-22 21:17:21 -07:00
HexLab98
c428e725aa fix(cli): honor custom_providers in preflight shrink warning
Classic /model confirmation already threaded custom_providers into the
context display, but the shrink-warning path did not. Probe-down then
matched the hardcoded "qwen" catalog (131072) and falsely warned that a
1M custom endpoint had shrunk — while the status bar still showed 1M.

Pass the same fresh inventory list used by switch_model/TUI (with
agent-snapshot fallback), and fall back to agent._custom_providers inside
merge_preflight_compression_warning when the kwarg is omitted.
2026-07-22 21:17:21 -07:00
HexLab98
b0b7f15598 test(computer-use): assert exact capture skips X11 active-window probe
Cover exact_target selection and capture(pid=, window_id=) so the
xprop helper is not invoked on the hot path.
2026-07-22 21:15:18 -07:00
HexLab98
9881328651 fix(computer-use): skip X11 active-window probe for exact targets
Limit the _NET_ACTIVE_WINDOW xprop fallback to unqualified default
captures so exact pid/window_id targeting does not pay up to a 2s
subprocess probe on Linux/X11.
2026-07-22 21:15:18 -07:00
HexLab98
a57bab1c84 test(computer-use): cover Linux X11 active-window selection
Direct xprop parse-boundary coverage plus tied/unknown z_index and
higher-z-frontmost regressions for #58026.
2026-07-22 21:15:18 -07:00
HexLab98
f320f3e5d7 fix(computer-use): prefer X11 active window when z_index ties
When Linux/X11 reports the same z_index for every on-screen window,
prefer _NET_ACTIVE_WINDOW via xprop instead of list order. Keep the
higher-z-index-is-frontmost contract when ordering is informative.
2026-07-22 21:15:18 -07:00
Teknium
ff6d86ed0f chore(contributors): map YLChen-007 bare-noreply email 2026-07-22 21:14:44 -07:00
Teknium
f31c10a0da test/docs(slack): force-clear new gating env vars; document new options
- Add SLACK_THREAD_REQUIRE_MENTION, SLACK_IGNORE_OTHER_USER_MENTIONS and
  SLACK_REQUIRE_MENTION_CHANNELS to the conftest behavioral-env force-clear
  list so config-loader side effects can't leak between tests (same class
  of leak the existing SLACK_* entries guard against).
- Document thread_require_mention and require_mention_channels in the
  Slack messaging guide next to the other mention-gating options.
2026-07-22 21:14:44 -07:00
Teknium
2c159f00e4 chore(contributors): add email mappings for slack C10 reply-storm salvage 2026-07-22 21:14:44 -07:00
LeonSGP43
0bbf679e4c test(slack): add peer-agent smoke target 2026-07-22 21:14:44 -07:00
Teknium
b416907538 feat(slack): require_mention_channels per-channel force-mention override
Port of the Slack half of #13855 (by @kshitijk4poor), reimplemented against
the plugin adapter (the original PR targets the deleted
gateway/platforms/slack.py and six other legacy adapters).

Channels listed in slack.require_mention_channels (config.yaml) or
SLACK_REQUIRE_MENTION_CHANNELS ALWAYS require an explicit @mention, even
when require_mention is false globally or the channel is in
free_response_channels — the opposite direction of free_response_channels.
Instead of duplicating the PR's inline reply-to-bot-thread/mentioned-thread/
session checks, the forced channel falls through to the SAME decision chain
as normal mention gating, so all five wake checks in
_should_wake_on_unmentioned_message keep applying (single decision path).

Credit: adapted from #13855 by @kshitijk4poor (Slack half only; the other
platform halves target deleted legacy adapters and are out of scope for
this cluster).
2026-07-22 21:14:44 -07:00
YLChen-007
38863322a4 Harden gateway mass mention handling 2026-07-22 21:14:44 -07:00
saitama
094c883bc0 Add Slack thread mention gating 2026-07-22 21:14:44 -07:00
Ben Phillips
f50e1e9c51 docs(slack): document ignore_other_user_mentions
Adds the option to the Mention & Trigger Behavior section: leading-
mention semantics, opt-in default, env var, and DM/MPIM scope (1:1 DMs
unaffected; MPIMs apply it like channels).

Claude-Session: https://claude.ai/code/session_01TKsNdptNdo9CqT2u7JMdkH
2026-07-22 21:14:44 -07:00
Ben Phillips
286ec6afb3 fix(slack): count pipe-form bot mentions as mentioned in ignore gate
The ignore_other_user_mentions gate relied solely on is_mentioned, which
only recognises exact <@UID> markup, so a message mentioning the bot in
pipe form (<@UID|name>) alongside a leading other-user mention was
wrongly suppressed. The gate now also scans for the bot's own mention in
either markup before ignoring a message.

Claude-Session: https://claude.ai/code/session_01TKsNdptNdo9CqT2u7JMdkH
2026-07-22 21:14:44 -07:00
Ben Phillips
7f9cab15d8 feat(slack): add ignore_other_user_mentions option
Once the bot is @mentioned in a Slack thread it auto-follows and replies
to every later message in that thread, including ones a human addresses to
another human (e.g. "@rasha check this out"). The bot butts in.

Add slack.ignore_other_user_mentions (env SLACK_IGNORE_OTHER_USER_MENTIONS,
default off). When on, a channel/thread message whose first token @mentions
someone other than the bot is treated as addressed to that person and the
bot stays silent unless it is also mentioned. This is Slack parity for the
Discord option of the same name (#33501), adapted to Slack's thread model:
the trigger is a leading mention ("addressed to"), so a message that merely
references another user mid-sentence still reaches the bot.

The gate sits ahead of the free-response / require_mention ladder so it also
overrides the mentioned-thread auto-follow. DMs are never filtered.
2026-07-22 21:14:44 -07:00