Commit graph

1638 commits

Author SHA1 Message Date
Willie Peacock
b9b5481d62 fix(kanban): preserve cross-profile project child routing 2026-07-23 08:34:06 -07:00
Willie Peacock
6833eabb53 fix(kanban): isolate worker-created child workspaces
Default kanban_create children now keep fresh scratch paths, while explicit dir sharing remains supported and project context resolves to a per-task worktree. Surface resolved workspace fields in create responses/events and cover scratch mutation, nesting, explicit sharing, and project inheritance.

Fixes #67567
2026-07-23 08:34:06 -07:00
Mustafa
dc3e4e8428 fix(kanban): keep delegated results in worker turn
Dispatcher-spawned Kanban workers are finite one-shot processes, so detached delegation completions can outlive their only consumer. Mark that runtime as unable to deliver async completions and reuse the synchronous delegation fallback, returning required child results before the worker exits.\n\nAlso make unsupported-session notes runtime-generic and cover the delayed-child lifecycle regression.\n\nRefs #63169
2026-07-23 08:33:55 -07:00
Dickson Neoh
08298dabbd fix(computer-use): handle Linux cua window metadata
Treat cua-driver's Linux `is_on_screen: null` as unknown instead of
off-screen, and skip GNOME Shell desktop/backdrop helper windows
(ding "Desktop Icons", @!x,y;BDHF) when selecting the default capture
target — they are targetable X11 windows but capture as empty.

Reconciled with the _NET_ACTIVE_WINDOW fallback from #58030: helper
windows are filtered out of the candidate pool first, then the tied
z-order active-window probe runs on the remaining real app windows.
Also falls back to the requested app name for _last_app when Linux
windows carry no app_name.

Salvaged from #54173 by @dnth.
2026-07-23 08:11:58 -07:00
Teknium
792ede0a36 test: pin repo root on PYTHONPATH for subprocess-boundary kanban isolation tests
The three subprocess tests spawn 'sys.executable -c' children that import
hermes_cli. From a worktree, the child resolved the MAIN checkout's editable
install instead of the tree under test, so the new DB/CLI guards appeared
missing and the tests failed with rc=0. Route the spawns through a helper
that pins the repo root under test on PYTHONPATH.
2026-07-23 07:33:36 -07:00
trkim
a7dcf9787b fix(kanban): harden delegated-child mutation boundary 2026-07-23 07:33:36 -07:00
trkim
47bbc12e18 fix(kanban): isolate delegated children from parent task 2026-07-23 07:33:36 -07:00
Brooklyn Nicholson
69b97a97f7 Merge remote-tracking branch 'origin/main' into bb/salvage-53841-no-overlay
# Conflicts:
#	tools/computer_use/cua_backend.py
2026-07-23 01:26:21 -05:00
Brooklyn Nicholson
8b6d34ad85 test(computer_use): pin resolved driver for update-check tests
cua_driver_update_check now short-circuits to None when no driver
resolves; CI has none installed, so pin resolve_cua_driver_cmd in the
update-check and env-sanitization tests.
2026-07-23 01:10:19 -05:00
David Metcalfe
f43ff5b4bb fix(computer_use): mock _cua_no_overlay in existing tests, fix platform-dependent assertions
- Add autouse fixture to TestMcpInvocationResolution to disable
  --no-overlay flag so existing tests assert baseline args
- Make test_config_load_failure_fails_safe and test_missing_section_enables
  platform-aware (Linux auto-detect returns True, macOS/Windows False)
2026-07-23 00:44:43 -05:00
Tianqing Yun
2f9d88caee fix: resolve cua-driver across computer-use surfaces 2026-07-23 00:42:46 -05:00
Tianqing Yun
030822b68d fix: resolve cua-driver from user-local install paths 2026-07-23 00:42:46 -05: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
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
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
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
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
kandotrun
4f726ed467 fix(slack): preserve media in standalone cron delivery 2026-07-22 20:57:25 -07:00
Teknium
e907ecccef test(search): prove discovery scope and bookend bounding work together on compacted sessions
Integration test for the two compaction layers landing this sweep:
#63144's discovery-scope fix (archived rows surface from the current
session) and #69334's bookend bounding (summary exclusion + content
caps). A single compacted session exercises both: the archived FTS hit
must surface while the compaction handoff at the session tail stays out
of bookend_end and long messages stay capped.
2026-07-22 16:59:09 -07:00
MacroAnarchy
98c0d8b291 fix(search): require compacted=1 not just active=0, scope delegation-under-compression
Addresses @teknium1's second review round on #63144:

1. _is_compacted_message now checks both active=0 AND compacted=1.
   Previously checked active=0 alone, which also matched rewind/undo rows
   (active=0, compacted=0) that must stay hidden.

2. Added _is_compression_ended() — checks only the session's own
   end_reason, not the lineage-wide has_compression_hop flag. This prevents
   delegation children living under a compression continuation from leaking
   through the lineage filter.

3. _discover lineage skip now uses is_ended_session (session-level check)
   instead of has_compression (lineage-level flag).

New tests:
- TestRewindExclusion: rewind rows stay hidden alongside compacted rows
- TestCompressionEndedHelper: session-level end_reason checks
- TestLegacyContinuationPlusDelegation: delegate child excluded while
  compression ancestors surface

78 passed (71 + 7 new).
2026-07-22 16:59:09 -07:00
MacroAnarchy
711f1c2f1a fix(search): surface compaction-archived and compression-parent sessions in discovery
After context compaction, pre-compaction content was invisible to
session_search — a memory black hole. The _discover() skip logic
filtered both same-session and same-lineage hits unconditionally,
without distinguishing compression-summarised content (gone from live
context) from delegation children (still visible to the parent agent).

Reworked _resolve_to_parent to return (root_id, has_compression_hop),
checking end_reason='compression' on every hop during the same
db.get_session() traversal — zero extra queries.

_discover() now has three compression-aware paths:
- In-place compaction: FTS hits on active=0 (compacted=1) rows pass
  through even when raw_sid == current_session_id
- Legacy rotation: lineage hits pass through when has_compression_hop
  is true on either side of the chain
- Delegation children: still excluded (no compression edge)

18 new tests covering all three scenarios + unit tests for the helpers.

Addresses Teknium's review feedback on #6256.
Closes #13840, #13841.
2026-07-22 16:59:09 -07:00
Brooklyn Nicholson
393c100a92 feat(voice): speech-interrupted latch in the TTS streaming core
mark_speech_interrupted() / take_speech_interrupted(): a one-shot,
TTL'd (120s) latch plus SPEECH_INTERRUPTED_NOTE. Barge-in paths mark
it when they cut live speech; the next turn's submit path pops it and
prepends the note to the model-bound message — API-call local, never
persisted, so history and prompt caching are untouched.
2026-07-22 17:53:06 -05:00
Brooklyn Nicholson
b135a8badd feat(voice): stream any provider in the CLI, with barge-in capture
The streaming gate broadens from ElevenLabs-only to any provider that
passes check_tts_requirements(). In continuous voice mode a mic monitor
runs during playback: talking over the agent cuts TTS at detection
while the monitor keeps recording, then the captured interruption is
transcribed and queued as the next turn (process_loop's auto-restart
stands down while the capture owns the mic). New voice.barge_in config
key, default true.
2026-07-22 17:47:15 -05:00
Brooklyn Nicholson
8ce18d2557 feat(voice): barge-in detector with pre-roll utterance capture
listen_for_speech(): sustained-RMS speech detection with the noise
floor calibrated against audible playback (speaker bleed doesn't
self-trigger). With capture=True it keeps a rolling pre-roll buffer and
records through to a silence endpoint, so the interruption is
transcribed from its first syllable — detection alone loses the opening
words to the detector's sustain window plus the mic re-open.
transcribe_recording() maps no_speech provider results to a successful
empty transcript (silence, not an error).
2026-07-22 17:47:05 -05:00
Brooklyn Nicholson
d8f30b1df7 fix(stt): flag empty transcripts as no_speech
An STT provider that hears no words is reporting silence, not failing.
ElevenLabs/xAI empty-transcript errors now carry no_speech so live
voice loops can re-listen quietly instead of surfacing an error on
every pause.
2026-07-22 17:47:04 -05:00
Brooklyn Nicholson
592effcb2a feat(tts): provider-agnostic streaming core with a shared sentence chunker
StreamingTTSProvider ABC + registry + resolve_streaming_provider() —
ElevenLabs (pcm_24000) and OpenAI (response_format=pcm) stream chunked
PCM; every other provider keeps its configured voice and falls back to
per-sentence sync synthesis. SentenceChunker is the one incremental
cutter every surface shares: sentence-boundary cuts on the delta
stream, <think> blocks stripped even when split across deltas, short
fragments merged forward so they never stall as tiny clips.
2026-07-22 17:46:55 -05:00
Brooklyn Nicholson
70ba3c4828 feat(desktop): agent can focus panes + shared desktop-UI event bridge
Extract the open_preview emitter into a shared tools/desktop_ui bridge
(one gateway-injected sink, routed by HERMES_UI_SESSION_ID) and add a
second desktop-gated tool on top of it:

- focus_pane(chat|files|terminal|review|sessions) -> pane.reveal event.
  The desktop runs each pane's own reveal path (revealDesktopPane table)
  and only acts on the active window -- a background turn never moves the
  user's focus (desktop AGENTS.md: offer, don't hijack).

open_preview now emits through the same bridge. Both tools are check_fn
on HERMES_DESKTOP (zero footprint elsewhere), sitting beside
read_terminal/close_terminal in _HERMES_CORE_TOOLS.

Deliberately not adding run_slash: letting the agent fire slash commands
mid-turn (/model, /new, /clear) fights prompt-cache + conversation
invariants.
2026-07-22 12:13:01 -05:00
Brooklyn Nicholson
f071f42244 feat(desktop): let the agent open the preview pane
Add a desktop-gated open_preview tool so 'open cnn.com in the preview
pane' works. The tool (check_fn on HERMES_DESKTOP, zero footprint
elsewhere) emits a preview.open event through a gateway-injected emitter,
mirroring the close_terminal -> terminal.close bridge. The desktop
handles it in usePreviewRouting, normalizing the target and opening the
pane for the active session only -- a background turn never hijacks it.

Bare domains and localhost are coaxed into fetchable URLs (www.cnn.com ->
https://, localhost:3000 -> http://); file paths and schemes pass through
to the renderer's normalizer.
2026-07-22 12:03:24 -05:00
liuhao1024
07cbb500b6 fix(clarify): reject arbitrary prose for native interactive multi-choice clarifies
In Slack threads, ordinary follow-up messages sent while a native
multi-choice clarify was pending were consumed as clarify answers —
_coerce_text_response accepted arbitrary text for any pending entry, so
the gateway text-intercept swallowed unrelated thread messages and the
user's messages appeared to be ignored.

Tighten resolve_text_response_for_session for native interactive
multi-choice prompts (buttons rendered, awaiting_text=False):

- numeric selections ("2") still resolve to the canonical choice
- exact choice-label matches (case-insensitive) still resolve
- arbitrary prose is now REJECTED (returns False) so the message
  continues as a normal turn instead of vanishing into the clarify

Behavior is preserved everywhere free text is legitimately the answer:
open-ended clarifies, explicit 'Other' text-capture mode, and the base
adapter's numbered-text fallback (which flips awaiting_text at send
time).

Salvaged from PR #62042 by @liuhao1024.

Fixes #62034
2026-07-22 07:00:47 -07:00
liuhao1024
9bb253d4fa fix(tools): filter compaction summaries from session_search bookends and cap content length
Context-compaction handoff summaries (prefixed with [CONTEXT COMPACTION])
were being returned as normal bookend_start/bookend_end messages in
session_search discovery mode. A single compaction handoff could be 57K+
chars, immediately bloating a fresh session prompt to 73K+ chars from one
search hit.

Changes:
- Add _COMPACTION_PREFIXES and _is_compaction_summary() helper
- Filter compaction summaries from bookend_start and bookend_end in _discover()
- Cap bookend content to 1200 chars and window content to 4000 chars
- Add content_truncated/original_content_chars metadata when truncation occurs
- Add 6 regression tests covering prefix detection, bookend filtering,
  content capping, and legacy [CONTEXT SUMMARY] prefix

Fixes #43175
2026-07-22 07:00:04 -07:00
Teknium
295e20358c
feat(delegation): let subagents use execute_code (#69325)
Children inherit the parent's env, repo, and toolsets but were denied
execute_code ('children should reason step-by-step, not write scripts').
That forces subagents doing mechanical multi-step work (batch file
reads, fetch-N-pages loops, filter-before-context reductions) to burn
reasoning iterations one tool call at a time.

- Remove execute_code from DELEGATE_BLOCKED_TOOLS
- Stop stripping the code_execution toolset from child bundles
- No recursion risk: the sandbox bridges only the 7 SANDBOX_ALLOWED_TOOLS
  (web/file/terminal) — delegate_task and execute_code itself are not
  reachable from inside a sandbox script
- Update schema text, AGENTS.md, and delegation docs
- Tests: blocked-constant, strip, and child-assembly tests updated;
  new test pins execute_code as intentionally unblocked
2026-07-22 06:58:45 -07:00
Andy
e89216e7f5 fix(secrets): harden encrypted Bitwarden cache 2026-07-22 04:40:07 -07:00
SeoYeonKim
2beed8b53d fix(mcp): pass secret-source-injected env vars to stdio servers
Surgical reapply of PR #37523 onto current main (the original branch
predates the SecretSource registry refactor).  _build_safe_env() now
forwards env vars tagged in env_loader._SECRET_SOURCES — widened from
Bitwarden-only to any registered secret source (Bitwarden, 1Password,
plugin backends), since the provenance map is source-agnostic.
Explicit server env: config still wins; untagged secrets stay filtered.

Fixes #37499.
2026-07-22 03:19:42 -07:00
brooklyn!
75be8fb463
test(mcp): stamp breaker-open time on the monotonic clock, not a literal (#69003)
test_session_expired_retry_waits_for_new_session hardcoded
_server_breaker_opened_at["hindsight"] = 123.0 to simulate a circuit breaker
whose cooldown has already elapsed. But the breaker in tools/mcp_tool.py
compares that stamp against time.monotonic() (age = monotonic() - opened_at,
elapsed when age >= _CIRCUIT_BREAKER_COOLDOWN_SEC). time.monotonic()'s origin
is arbitrary and small on a freshly-booted CI container, so age worked out to
only a few seconds there (< the 60s cooldown) — the breaker stayed open, the
half-open probe never fired, and the retry returned the "unreachable" error
instead of "bank ok". It passed on long-uptime dev boxes (large monotonic)
and failed under CI, with the reported "Auto-retry available in ~Ns" drifting
run to run as the container's monotonic clock varied.

Stamp opened_at relative to the same clock the code reads
(time.monotonic() - _CIRCUIT_BREAKER_COOLDOWN_SEC - 1.0) so the cooldown is
provably elapsed regardless of the monotonic origin, exercising the intended
half-open transition deterministically.
2026-07-21 19:54:40 -05:00
Teknium
fbe086f7cc fix(mcp): cycle-guard cause/context traversal in transport classifier
Builds on diffen77's #66547 (cherry-picked as the previous commits).
Extend _is_session_expired_error's iterative traversal to follow
__cause__/__context__ in addition to ExceptionGroup .exceptions — SDK
wrappers often raise a generic RuntimeError *from* the message-less
ClosedResourceError, leaving the transport signal reachable only via
the chain. The identity-visited set guards chain cycles (handlers
re-raising previously seen exceptions), and a bounded node budget
(_EXC_TRAVERSAL_MAX_NODES) caps pathological acyclic graphs.

Adds regression tests: cause/context chain detection, interruption
precedence through chains, cyclic cause/context termination, and
budget-bounded termination.
2026-07-21 12:43:27 -07:00
Börje
473545449b fix(mcp): make transport classification cycle safe 2026-07-21 12:43:27 -07:00
Börje
1ae1dd8b2c fix(mcp): harden nested interruption detection 2026-07-21 12:43:27 -07:00
Börje
80209e51db test(mcp): isolate resource error breaker state 2026-07-21 12:43:27 -07:00
Börje
f26fb901ea fix(mcp): reconnect message-less closed transports 2026-07-21 12:43:27 -07:00
Teknium
ee23bffee9 fix(mcp): clear connect-cooldown state on every shutdown path
Builds on trevorgordon981's #50589 (cherry-picked as the previous commit).
The #50394 cooldown reset only ran inside the async _shutdown coroutine,
which is skipped on the empty-_servers fast path — the most common state
when a server failed to connect (failed servers are never recorded in
_servers). It was also skipped when the MCP loop wasn't running.

Clear _server_connect_retry_after/_server_connect_failures on the fast
path and in a final unconditional sweep so a full shutdown/restart always
re-attempts every configured server immediately. Adds regression tests
for both paths.
2026-07-21 12:43:27 -07:00
trevorgordon981
2c3aa3f7b8 fix(mcp): isolate a single failing stdio server from the bridge (#50394) 2026-07-21 12:43:27 -07:00
yungchentang
106d1822e3 fix(mcp): re-register tools during parked revival 2026-07-21 12:43:00 -07:00
Teknium
da8b89c835 fix(mcp): one WARNING per state transition, DEBUG retry chatter, jittered backoff
Log-storm hygiene for the reconnect machinery (#65673, #66092):

- State transitions carry exactly one WARNING each:
  connected → degraded (keepalive failure), degraded → parked
  (budget exhausted / rapid-drop park / permanent error),
  parked → connected (revival proven healthy, via
  _mark_session_proven).
- Per-attempt retry logs (initial-connect attempts, connection-lost
  reconnect attempts, per-cycle rebuild notices, park-wake probes)
  demoted to DEBUG.
- Backoff sleeps get +/-20% uniform jitter (_jittered) so a herd of
  servers that lost the same backend doesn't retry — and log — in
  lockstep.
2026-07-21 12:42:33 -07:00
Teknium
3e6b437762 fix(mcp): unwrap exception groups and park permanent failures immediately (#65673)
- Add module-level _unwrap_exception_group (ported from
  hermes_cli/mcp_config.py, adapted): handles nested groups, prefers
  non-cancellation leaves over the CancelledErrors anyio sprays across
  sibling tasks, and re-raises KeyboardInterrupt/SystemExit leaves.

- Add _classify_mcp_failure(exc) -> 'permanent'|'transient'.
  Permanent: auth 401/403, NonMcpEndpointError, InvalidMcpUrlError,
  FileNotFoundError/ENOENT on the stdio command. Transient: everything
  else (network/EOF/ClosedResource/TaskGroup drops). Permanent failures
  park immediately without burning the retry ladder — every retry
  against a missing binary or a revoked credential hits the same wall.

- Every log site in run() and the keepalive failure log now logs the
  unwrapped root cause as 'TypeName: message', so a dead stdio pipe
  says 'BrokenPipeError' instead of the opaque
  'unhandled errors in a TaskGroup (1 sub-exception)' (and empty
  str(exc) dead-pipe errors are no longer blank).
2026-07-21 12:42:33 -07:00
Teknium
48be1068bb fix(mcp): charge immediate reconnects against a rapid-drop budget (#62212)
Harden the immediate-reconnect path from #66271:

- _reconnect_or_reraise_group re-raises when the transport TaskGroup
  carries a KeyboardInterrupt / SystemExit leaf — fatal signals must
  propagate to the interpreter, never be converted into a reconnect.

- Rapid-drop budget: a completed handshake alone no longer clears
  _reconnect_retries/backoff on clean transport return. A session is
  UNPROVEN until it demonstrates real health — survived >=1 full
  keepalive interval (keepalive success path) or served >=1 successful
  tool call (_mark_session_proven). Unproven clean returns are charged
  against _MAX_RECONNECT_RETRIES, so a flapping transport that
  handshakes fine and drops moments later still reaches the park
  instead of respawning forever (#62212: 6212 spawns in 63h).
  Proven sessions keep the #57604 behaviour: budget clears, transient
  blips over a long-lived session never accumulate toward parking.
2026-07-21 12:42:33 -07:00
hanyu1212
a8a93b6c68 fix(mcp): reconnect immediately on transport TaskGroup drop instead of backoff/park
Streamable-HTTP / SSE MCP transports run their stream pump inside an anyio
TaskGroup. A transient stream drop (idle timeout, brief backend blip,
server-side TCP close) surfaces as a BaseExceptionGroup escaping the
transport context manager. It reached run()'s error path, which applied
exponential backoff (1s..16s) and eventually parked the server for 300s and
deregistered its tools — turning a sub-second glitch into a multi-minute
tool outage even though the POST path was still healthy.

_run_http now wraps all three transport branches (SSE, new + deprecated
Streamable-HTTP) and routes a BaseExceptionGroup through a new
_reconnect_or_reraise_group() helper that returns 'reconnect' (immediate
rebuild, no backoff/park/deregister). It re-raises instead of masking when
shutdown is in progress, when the group carries a real CancelledError
(cancellation must propagate, cf. #9930), or when no live session was
established this attempt (a connect/handshake failure that should fall
through to run()'s backoff rather than hot-loop).

Fixes #66092

Co-authored-by: 雨哥 <hanyu1212@users.noreply.github.com>
2026-07-21 12:42:33 -07:00
Teknium
9cc475cc58 test(approval): cover allow_session tiers in Matrix reaction seeding and gateway payload
Update the Matrix reaction-seeding contract to the four-reaction default
(once/session/always/deny), add tirith-tier (session without always) and
no-session-tier cases, and assert allow_session=True in the tirith
gateway payload.
2026-07-21 12:04:47 -07:00
Teknium
a31a31826c
fix(approval): raise gateway approval timeout to 300s, honest stale-tap UX, offer Always on mixed prompts (#68597)
Three related messaging-approval fixes:

1. approvals.timeout default 60 -> 300. PR #63501 collapsed the gateway
   wait onto the canonical approvals.timeout (previously
   gateway_timeout=300), silently shrinking messaging approval windows
   to 60s. Push-notification approvals routinely arrive later than a
   minute; taps landed after the wait had already failed closed.

2. Stale-tap honesty: adapters resolved the approval AFTER rendering
   '<checkmark> Approved by <user>' (Telegram/Discord/Slack), or ignored a zero
   resolve count (WhatsApp Cloud/Feishu). A tap on an expired prompt
   claimed approval while the command had already been denied. All
   button paths now resolve first and render 'Approval expired -
   command was not run' when nothing was waiting.

3. Mixed-warning prompts (dangerous pattern + tirith finding) now offer
   Always: the persistence layer already permanently allowlists the
   pattern key and downgrades the tirith key to session scope, but the
   UI hid Always whenever ANY tirith warning was present. Pure-tirith
   prompts still withhold Always (content findings are session-max by
   design), and Smart-DENY overrides remain once-only.
2026-07-21 05:41:41 -07:00
Teknium
03841c9658
fix(tools): make the tool-search context gate provider-aware (#68589)
_resolve_active_context_length() called get_model_context_length() with the
model id alone, so provider-enforced windows (e.g. Codex OAuth's 272K for
gpt-5.x vs the direct API's 1.05M) never reached the tool-search activation
gate — it sized against generic metadata for the same slug.

Resolve the runtime provider for the configured model and pass provider,
base_url, and api_key through. If credential resolution fails (offline, no
keys), degrade to a provider+base_url-only lookup so the static
provider-aware fallbacks still apply; explicit model.context_length keeps
short-circuiting as before (#46620). Gap flagged during review of #16735.
2026-07-21 05:27:23 -07:00