Commit graph

14508 commits

Author SHA1 Message Date
kshitijk4poor
b2c55582ef test(compressor): drop source-string guardrail tests
The two TestSourceGuardrail tests asserted the presence of literal
strings ("#58753", "_user_survives") in context_compressor.py. Those
are change-detector tests that break on any refactor without catching a
real regression. The four behavioral tests in
TestCompressAlwaysKeepsAUserTurn already exercise the real compress()
path and fully cover the invariant (user turn survives, summary pinned
to user, no consecutive user roles, surviving tail user untouched).
2026-07-05 21:42:19 +05:30
HexLab98
10ced05676 test(compressor): pin the zero-user-turn compaction guard (#58753)
Regression coverage for the kanban-worker crash where compression left a
transcript with no user-role messages, triggering a non-retryable
`400 No user query found in messages` from vLLM/Qwen.

Exercises the real `compress()` path with the reporter's shape (no system
prompt in the list, a re-compaction with the only user turn in the
compressed middle) and asserts the output always keeps >=1 user turn,
never introduces consecutive user roles, and leaves a surviving tail user
message untouched. A source guardrail pins the guard so a future refactor
cannot silently drop it.
2026-07-05 21:41:45 +05:30
HexLab98
24add1db74 fix(compressor): keep a user turn when compression would drop the last one
Compression could produce a transcript with ZERO user-role messages,
which OpenAI-compatible backends (vLLM/Qwen) reject with a non-retryable
`400 No user query found in messages`. This crashes `hermes kanban`
workers unrecoverably: every resume replays the same poisoned history and
fails on the very first request after a successful compaction.

The existing #52160 guard pins the handoff summary to role="user" only
when `last_head_role == "system"` — i.e. when the system prompt sits
inside `messages` (the gateway `/compress` path). The main
auto-compression path prepends the system prompt at request-build time,
so the list handed to `compress()` starts with a user/assistant turn,
`last_head_role` defaults to "user", and the summary is emitted as
role="assistant". A kanban worker seeded with a single short
`"work kanban task <id>"` prompt followed by nothing but assistant/tool
turns therefore ends up user-less once that early turn is summarised.

Generalise the guard: when no user-role message survives in the protected
head or the preserved tail, force the summary to carry role="user" so the
request always has at least one user turn. When a user does survive
(e.g. in the tail), the guard does not fire, so alternation is preserved.

Fixes #58753.
2026-07-05 21:41:44 +05:30
Teknium
605727e3b4
feat(discord): optional admin-only gate for exec-approval buttons (#51751)
Add an opt-in toggle (require_admin_for_exec_approval, default false) that
restricts who can click Approve/Deny on a dangerous-command prompt to admins
listed in allow_admin_from. Off by default, so the v0.16-restored user-scope
behavior is unchanged. When on, the clicker must pass the normal admission
check AND be an admin; fails closed (logged) when no admins are configured.
Only ExecApprovalView is gated — model picker / clarify / update-prompt stay
user-scope.
2026-07-05 06:42:42 -07:00
Teknium
8a04b516a8
Port from cline/cline#11803: recursively normalize JSON-string tool args by schema (#52220)
coerce_tool_args only repaired the outermost value, so JSON-encoded
*elements* of array properties (and nested object sub-fields) were left
as strings. Three core tools have array<object> schemas — todo.todos,
delegate_task.tasks, memory.operations — so a model emitting
{"todos": ["{...}"]} would pass raw JSON strings into the tool and fail
downstream on item["id"]/item["goal"] access.

Adds a schema-guided recursive pass (_normalize_json_strings_for_schema)
that parses JSON-string array items and nested object fields only when
the matching schema position expects an array/object, preserving
legitimate JSON-looking string fields (type: string).

Adapted from cline/cline#11803 to hermes-agent's existing coercion layer.
2026-07-05 06:42:28 -07:00
teknium1
b3b1e58ad6 fix(codex): stream commentary deltas through the reasoning channel
Follow-up to the salvaged #58696 (devatnull) + #41343 (annguyenNous)
commits: instead of fully suppressing commentary/analysis-phase stream
deltas, fire on_reasoning_delta so the CLI/gateway display them like
thinking text. Matches Codex CLI semantics where commentary is never
the turn's final answer, while keeping the narration visible in the
reasoning display. Adds devatnull to AUTHOR_MAP.
2026-07-05 06:29:45 -07:00
annguyenNous
538173f679 fix(codex): route commentary-phase preamble text to reasoning channel (fixes #41293)
GPT-5.x models on the Codex Responses API emit short pre-tool-call
"preamble" text as message items with phase="commentary". Previously,
_normalize_codex_response() added ALL message items to content_parts
regardless of phase, causing commentary text to leak as visible
assistant content on chat gateways.

Fix: when normalized_phase is "commentary" or "analysis", route the
message text to reasoning_parts instead of content_parts. This keeps
preamble/internal planning in the reasoning channel where it belongs.

Fixes NousResearch/hermes-agent#41293
2026-07-05 06:29:45 -07:00
devatnull
ea125dd62e fix: keep Codex commentary phase out of user-visible text 2026-07-05 06:29:45 -07:00
teknium1
372c0b5f45 chore: add devatnull to AUTHOR_MAP for PR #58700 salvage 2026-07-05 06:29:26 -07:00
devatnull
14c91ade32 fix: normalize display boolean strings 2026-07-05 06:29:26 -07:00
devatnull
b9de7044aa fix: preserve log tool-progress mode with status phrases 2026-07-05 06:29:26 -07:00
devatnull
d111faa3a7 fix: preserve busy steer env override 2026-07-05 06:29:26 -07:00
devatnull
12f03b11ff feat: make busy steer ack configurable 2026-07-05 06:29:26 -07:00
devatnull
46fbd73f66 fix: strip tool progress display modes 2026-07-05 06:29:26 -07:00
devatnull
fddc95f4c2 chore: limit generic status phrases to long-running notifications 2026-07-05 06:29:26 -07:00
devatnull
4bf5b563bd feat: add generic gateway status phrases 2026-07-05 06:29:26 -07:00
teknium1
b0f2bdbe8b fix(whatsapp): gate poll-vote events to Hermes-created polls + salvage follow-ups
- bridge: only enqueue poll_update events for polls Hermes itself created
  (tracked via recentlySentIds when /send-poll returns) so arbitrary human
  polls in group chats don't inject agent-visible messages on every vote
- update test_already_whatsapp_italic for the new markdown-italic mapping
- AUTHOR_MAP entry for @devatnull (PR #58704 salvage)
2026-07-05 06:27:20 -07:00
devatnull
11627fdcb9 feat(whatsapp): native Baileys polls, clarify-as-poll, locations, and rich inbound metadata
Salvaged from PR #58704 by @devatnull, scoped to the WhatsApp surface:
- bridge_helpers.js: pure, tested extraction of inbound Baileys message
  parsing (quoted text, MIME/filename, PTT vs audio, stickers, contacts,
  reactions, polls, locations, GIF playback metadata)
- native poll primitive: /send-poll endpoint, poll messageSecret caching,
  encrypted vote decryption + aggregation via Baileys
- send_clarify() renders multi-choice clarify prompts as native polls;
  votes flow back through the existing clarify text-intercept
- send_location() + /send-location for native WhatsApp location pins
- structured quoted-reply context (fixes duplicated '[Replying to: ...]'
  rendered both by the adapter and gateway/run.py)
- outbound formatting: markdown *italic* -> WhatsApp _italic_, invisible
  unicode sanitization; execSync -> execFileSync hardening; GIF -> mp4
  gifPlayback conversion with truthful image/gif fallback

Out of scope (deliberately not salvaged from #58704): cross-platform
ordered-delivery machinery in gateway/platforms/base.py, LOCATION: and
hermes:poll response-text directives (no prompt wiring exists yet), and
the unconditional WhatsApp reply-anchor suppression.
2026-07-05 06:27:20 -07:00
teknium1
0ca2a927cf chore: add devatnull to AUTHOR_MAP for PR #58697 salvage 2026-07-05 06:12:49 -07:00
Kong
558001307a feat(desktop,docs): surface stt.echo_transcripts in desktop settings and docs
Adapted from PR #53038 (stt.echo) to the stt.echo_transcripts key:
- desktop Voice settings section gains the Echo Transcripts toggle with
  label + description copy
- configuration.md documents stt.enabled / stt.echo_transcripts
2026-07-05 06:12:49 -07:00
devatnull
4be749d151 fix: honor top-level STT transcript echo config 2026-07-05 06:12:49 -07:00
devatnull
406eb719c3 fix: gate interrupt STT transcript echoes 2026-07-05 06:12:49 -07:00
devatnull
bfc5262725 feat: add STT transcript echo toggle 2026-07-05 06:12:49 -07:00
teknium1
95fc3c6b45 chore: add alastraz to AUTHOR_MAP for PR #41383 salvage
Some checks are pending
CI / Detect affected areas (push) Waiting to run
CI / Python tests (push) Blocked by required conditions
CI / Python lints (push) Blocked by required conditions
CI / TypeScript (push) Blocked by required conditions
CI / Docs Site (push) Blocked by required conditions
CI / Deny unrelated histories (push) Blocked by required conditions
CI / Check contributors (push) Blocked by required conditions
CI / Check uv.lock (push) Blocked by required conditions
CI / Lint Docker scripts (push) Blocked by required conditions
CI / Build&Test Docker image (push) Blocked by required conditions
CI / Supply-chain scan (push) Blocked by required conditions
CI / OSV scan (push) Waiting to run
CI / All required checks pass (push) Blocked by required conditions
CI / CI timing report (push) Blocked by required conditions
Deploy Site / deploy-vercel (push) Waiting to run
Deploy Site / deploy-docs (push) Waiting to run
2026-07-05 05:36:31 -07:00
Adrian Lastra
519ec7b3b3 fix(computer_use): parse (label) and = "value" AX element label forms
The SOM/AX element list dropped labels for two extremely common cua-driver
render forms, leaving the model unable to target elements by name:
  - [79] AXButton (Dark)              -> parenthesised label
  - [4]  AXStaticText = "Wi-Fi"       -> = "value" form
  - [92] AXPopUpButton = "Automatic"  -> = "value" form
The old regex only matched quoted "label" and id=Label, so System Settings
buttons/text/popups all surfaced with empty labels. That's why selecting the
macOS Appearance 'Dark' button by element index required guessing — the
labels weren't available to aim with.

Fix: extend _ELEMENT_LINE_RE to capture all four label forms (= "value",
"quoted", (parenthesised), id=Label), skipping a pure-digit (N) order number
in favour of the id= label. Verified live against System Settings: the
Appearance buttons now surface as Auto/Light/Dark.

Adds a regression test covering all label forms. Full suite: 84 passed.
2026-07-05 05:36:31 -07:00
Adrian Lastra
13b75e73ff fix(computer_use): re-fetch via CLI when MCP returns silent-empty captures
The first fix handled the EAGAIN McpError path. But the persistent MCP
session (long-running gateway/desktop worker) has a second failure mode:
list_windows or get_window_state 'succeed' over MCP yet return a
degenerate/empty payload (no windows, or no screenshot + blank tree)
WITHOUT raising — typically when the bridge reconnected mid-call and
dropped the heavy response. That surfaced to the model as a silent 0x0
capture with no error and no fallback firing (0.00s empty return).

Fix: detect empty results in capture() and re-fetch over the CLI
transport before giving up:
  - empty list_windows -> CLI re-fetch the window list
  - empty get_window_state (som/ax) -> CLI re-fetch the AX tree + screenshot
  - empty screenshot (vision) -> CLI re-fetch get_window_state for the PNG

Adds 2 regression tests. Full suite: 83 passed.
2026-07-05 05:36:31 -07:00
Adrian Lastra
7af9abd174 fix(computer_use): fall back to CLI transport when cua-driver MCP bridge hits EAGAIN
The cua-driver MCP stdio bridge intermittently (and on some machines
persistently) fails to forward heavier calls like get_window_state to
the daemon with POSIX EAGAIN — 'daemon transport error forwarding
get_window_state: Resource temporarily unavailable (os error 35)'.
The wrapper surfaced this as an empty 0x0 capture, so computer_use
returned blank screenshots even though the display, permissions, and
the daemon were all healthy (the direct 'cua-driver call' CLI path
worked fine throughout).

Fix: when the MCP path raises the transient/transport error, fall back
to the 'cua-driver call' subprocess transport, which talks to the
daemon over a different socket. The CLI fallback routes get_window_state
screenshots to a temp file via screenshot_out_file (tiny JSON response
instead of a multi-MB base64 blob that congests the socket), reads the
PNG back, retries with backoff, and remaps the JSON into the same
{data, images, structuredContent, isError} shape the MCP path produces
so capture()/_action() are transport-agnostic.

Adds _is_transient_daemon_error() classifier and 3 regression tests.
Verified live: captures that returned 0x0 now return full
1567x905 screenshots with the AX element tree.
2026-07-05 05:36:31 -07:00
Teknium
de4310c8f6
fix(computer-use): report the wedged startup phase in the session ready-timeout error (#58801)
The 'never reached ready' error (issue #57025) was undiagnosable — doctor
and MCP test pass while the wrapper times out, with no hint where startup
stalled. Track a phase marker through _lifecycle_coro (binary-check →
manifest-discovery → mcp-initialize → capability-discovery → ready) and
include it in the timeout RuntimeError plus a pointer to doctor and the
agent.log phase timings.

Complements the 15s→30s bump + success-path phase timing log from #58760.
2026-07-05 05:36:09 -07:00
Teknium
24a7546918
fix(cli): drop shell=True from cua-driver installer — download to mkstemp, exec as argv (#58796)
Replaces the POSIX `/bin/bash -c "$(curl …)"` invocation with a
download-then-exec flow: curl the upstream install.sh into a mkstemp
temp file (unpredictable name, 0600) and run it as a plain argv list.
No shell=True, no command substitution. The temp script is removed in
a finally block; download failures return cleanly without exec.

Salvages the intent of #34974 by @ErnestHysa. His original patch
targeted a fixed /tmp/cua-driver-install.sh path (symlink/TOCTOU-prone
on multi-user hosts) and predates Windows/Linux installer support;
this version uses mkstemp and keeps the powershell path untouched.

Co-authored-by: ErnestHysa <takis312@hotmail.com>
2026-07-05 05:35:44 -07:00
Teknium
2c0820c9ff
feat(cli): autocomplete + ghost text for stacked slash-skill invocations (#58763)
Follow-up to #57987: after /skill-a the completer previously went silent
for a second /skill token. Now, while the leading tokens form an unbroken
skill chain (each token a distinct installed skill, under the 5-cap) and
the word under the cursor starts with '/', the completer keeps offering
the remaining skill commands, and SlashCommandAutoSuggest ghost-suggests
the rest of the next skill name. Instruction text, path-like tokens, and
broken chains get no suggestions. The TUI's complete.slash RPC reuses
SlashCommandCompleter, so it inherits the behavior with no changes.
2026-07-05 04:34:14 -07:00
Teknium
1c156736dc
docs: warn that mid-session model switches break prompt caching (#58747)
/model switches, primary-model fallback, and credential-pool key
rotation all change the prompt-cache key (model and/or account), so
the next turn re-reads the entire conversation at full input price.
Add cost warnings everywhere docs recommend or describe these paths:

- reference/slash-commands.md: cost note on both /model rows
- user-guide/features/fallback-providers.md: warning admonition
- user-guide/features/credential-pools.md: warning admonition
- user-guide/configuring-models.md: mid-session switch warning
- guides/tips.md: expand cache tip + /model tip
- reference/faq.md: warning on the switch-back-and-forth example
- user-guide/desktop.md: composer picker bullet
- developer-guide/context-compression-and-caching.md: new
  cache-aware design pattern (model identity is part of the key)
2026-07-05 04:34:05 -07:00
Teknium
7fde19afcc
fix(cli): unwedge cua-driver installer timeouts — group-kill, stale-lock pre-clear, 660s ceiling (#58767)
* fix(cli): unwedge cua-driver installer timeouts — group-kill on timeout, stale-lock pre-clear, 660s ceiling

The cua-driver refresh in hermes update could wedge permanently:
subprocess timeout (300s) killed only the outer shell, orphaning the
curl|bash grandchildren and the upstream installer's concurrent-install
lock (~/.cua-driver/packages/.install.lock.d). The installer only
reclaims a stale lock after 600s of waiting — longer than our old
ceiling — so every subsequent run was killed before recovery could
fire: 'always times out'.

- Run the installer in its own process group (start_new_session) and
  SIGKILL the whole group on timeout, so no lock-holding orphans survive.
- Pre-clear a provably-stale lock (dead holder pid, or pid-less and
  older than the upstream 600s window) before invoking the installer.
- Raise the ceiling to 660s (> upstream LOCK_STALE_AFTER_SECONDS=600).
- Timeout message now names the lock path and the manual re-run command.

Fixes #58762

* chore: suppress windows-footgun lint on platform-gated kill calls

Both sites are POSIX-only: _clear_stale_cua_install_lock early-returns
on win32, and os.killpg sits in the 'not is_windows' branch.
2026-07-05 03:16:06 -07:00
liuhao1024
d537d29a6f fix(computer-use): increase cua-driver session startup timeout from 15s to 30s
On Windows, the cua-driver MCP session initialization can exceed the 15s
timeout due to manifest subprocess discovery + MCP transport setup.
This makes the computer_use tool permanently unavailable even though
hermes computer-use doctor and hermes mcp test both pass.

- Increase _ready_event.wait timeout from 15s to 30s
- Add startup timing instrumentation (manifest + mcp_init durations)
- Log timing at INFO level for diagnosability

Fixes #57025
2026-07-05 03:15:49 -07:00
Sami Rusani
d8b51269ca fix(update): skip cua-driver refresh when Applications is unwritable 2026-07-05 03:15:06 -07:00
dsad
c13281ab57 Guard native image routing with file safety 2026-07-05 03:15:03 -07:00
teknium1
51c1ba6976 fix(agent): apply pool-level keepalive to the process_bootstrap sibling builder
The salvaged #54550 converted AIAgent._build_keepalive_http_client but the
near-identical build_keepalive_http_client in agent/process_bootstrap.py
(used by auxiliary clients: compression, vision, web_extract, titles) kept
the socket_options transport and the api.githubcopilot.com bypass. Same
conversion: httpx.Limits(keepalive_expiry=20) + pool timeouts, verify
forwarded on client and no-proxy mounts, copilot hardcode removed.
2026-07-05 03:14:55 -07:00
David Metcalfe
8324dd19ca fix(agent): replace custom socket_options transport with httpx pool-level keepalive expiry
The custom ``httpx.HTTPTransport(socket_options=[SO_KEEPALIVE, ...])``
in ``_build_keepalive_http_client()`` was introduced to fix CLOSE-WAIT
socket accumulation on long-lived connections (#10324).

That approach broke streaming for providers behind reverse proxies
(OpenResty, Cloudflare, etc.) because the custom socket options
conflict with the proxy's chunked-transfer handling (#54049, #12952).
It also stripped TCP_NODELAY, stalling TLS handshakes and SSE encoding.
Narrow per-provider bypasses were added for Copilot (#50298), Codex
(#36623, #12953), but the root cause remained.

The fix moves connection lifecycle management from the socket layer to
the HTTP pool layer:

- ``httpx.Limits(keepalive_expiry=20.0)`` tells httpx to close idle
  pooled connections at 20 s, before a reverse proxy's typical 30-60 s
  timeout drops them and causes CLOSE-WAIT accumulation.
- The default httpx transport preserves OS TCP defaults (including
  TCP_NODELAY), so TLS handshakes and SSE chunked encoding work
  correctly.
- ``trust_env=False`` prevents httpx from double-dipping on env vars
  (we handle proxy detection ourselves via ``_get_proxy_for_base_url``
  which respects NO_PROXY).
- The Copilot host bypass (line 3632) is no longer needed since all
  providers now use the same standard httpx.Client.

Closes #54049.  Supersedes #12010, #36623, #12953, #50298.
2026-07-05 03:14:55 -07:00
dsad
e02cef0d0d fix(memory): guard local uploads against credential reads 2026-07-05 03:14:49 -07:00
MorAlekss
d577408f3f fix(webhook): reject generic V2 signature missing timestamp instead of falling back to V1 2026-07-05 02:25:38 -07:00
Teknium
ebfc49c4d9
fix(approval): require exact ./.. segments in the root-collapse hardline token (#56179)
Follow-up to #56236: the broadened root token /[/.]*\** treats any run of
dots after the root slash as a collapse spelling, so a literal root-level
directory named '...' (rm -rf /...) was unconditionally hardline-blocked
with no approval path. Tighten the token to /(?:(?:\.\.?)?/)*(?:\.\.?)?\**
so each inter-slash segment must be exactly '.' or '..' — all real collapse
spellings (//, /., /./, /.., //*, ///, /../..) stay on the hardline floor
while literal dot-run dirs fall through to the softer DANGEROUS_PATTERNS
rules like every other real path.
2026-07-05 02:24:00 -07:00
Teknium
cb6c47af08
feat(approvals): /deny <reason> relays denial reason to the agent (port nanoclaw#2832) (#54518)
* feat(approvals): /deny <reason> relays denial reason to the agent

Port from qwibitai/nanoclaw#2832 (reject with reason).

Gateway /deny now accepts an optional trailing reason (/deny <reason>
or /deny all <reason>). The reason rides on the per-session approval
entry through resolve_gateway_approval -> _await_gateway_decision and is
appended to the BLOCKED tool result the agent receives, so a declined
agent can adapt instead of only hearing 'denied'.

Adapted to hermes-agent's synchronous single-command /deny model: no DB
state, no second-message capture step, no migration. Reason is capped at
280 chars and threaded through both the terminal-command guard and the
execute_code guard. Plain /deny and the approve paths are unchanged.

- tools/approval.py: _ApprovalEntry.reason; resolve_gateway_approval gains
  optional reason; _await_gateway_decision returns it; both gateway BLOCKED
  messages include it
- gateway/slash_commands.py: parse leading 'all' + trailing reason
- locales/en.yaml: deny.denied_reason_{singular,plural}
- hermes_cli/commands.py: /deny args_hint '[all] [reason]'
- tests: 3 new (with-reason, all+reason, plain-deny regression)

* fix(ci): localize deny-reason keys across all locales + update interrupt-path assertions

CI surfaced two enforced invariants broken by the deny-with-reason change:
- test_i18n catalog-parity requires every locale to carry the same keys as
  en.yaml with matching placeholders. Added deny.denied_reason_singular/plural
  (with {count}/{reason}) to all 15 non-English locales.
- test_approval_interrupt asserts the exact dict from _await_gateway_decision,
  which now carries a 'reason' key (None on the interrupt/timeout paths).
2026-07-05 02:22:08 -07:00
Teknium
9767e19b60
feat(skills): stacked slash-skill invocations — /skill-a /skill-b do XYZ (#57987)
Inspired by Claude Code v2.1.199 (July 2, 2026): stacked slash-skill
invocations load all leading skills (up to 5), not just the first.

- agent/skill_commands.py: split_stacked_skill_commands() consumes leading
  /skill tokens (stops at the first non-skill token so slash-path arguments
  are never swallowed); build_stacked_skill_invocation_message() composes
  the multi-skill turn reusing the existing bundle scaffolding markers so
  extract_user_instruction_from_skill_message() keeps memory providers
  storing the user's instruction, not N skill bodies.
- cli.py + gateway/run.py: dispatch the stacked path on both surfaces.
- 11 new tests + docs section in skills.md.
2026-07-05 02:20:01 -07:00
luyifan
30479961b8 fix(gateway): tolerate punctuation on silence markers 2026-07-05 02:12:26 -07:00
Teknium
edf8e0ba94
feat(mcp): surface MCP server log notifications in agent.log (#57416)
Port from anomalyco/opencode#34529: MCP servers can emit
notifications/message logging notifications (RFC 5424 levels), but the
MCP SDK's default logging_callback silently discards them — server-side
warnings/errors during tool calls were invisible.

- tools/mcp_tool.py: pass a logging_callback to every ClientSession
  (stdio, SSE, streamable HTTP old+new API paths via the shared
  sampling_kwargs sites), mapping the 8 MCP log levels onto Python
  logging levels and tagging entries with [server/logger] origin.
- JSON-serialize non-string payloads, cap at 2000 chars so a chatty
  server can't flood agent.log, never raise from the handler.
- Gated on SDK support (_check_logging_callback_support) mirroring the
  existing message_handler gate for old SDK versions.
- tests/tools/test_mcp_server_log_notifications.py: 10 tests covering
  level mapping, origin tagging, JSON payloads, truncation, and the
  never-raise contract.
2026-07-05 02:06:39 -07:00
Teknium
4751af0a0b
feat(errors): fail fast on TLS certificate verification failures with fix hints (#57992)
Inspired by Claude Code v2.1.199 (July 2, 2026): SSL certificate errors
(TLS-inspecting proxies, missing CA bundles, expired certs) no longer
burn retries before showing actionable guidance — they fail immediately
with the fix hint.

- agent/error_classifier.py: new FailoverReason.ssl_cert_verification +
  _SSL_CERT_VERIFY_PATTERNS, checked BEFORE the transient-SSL patterns
  (cert-verify messages also contain '[SSL:' and previously retried
  forever as timeout). Non-retryable, no compression, no fallback churn.
- agent/conversation_loop.py: dedicated status line + per-cause fix
  hints (corporate proxy CA bundle, certifi refresh, self-signed local
  endpoints) on the non-retryable abort path.
- 7 new tests incl. regression guards (transient alerts still retry,
  large-session cert failure doesn't trigger compression).
2026-07-05 02:05:51 -07:00
teknium1
619db0175d fix(gateway): move PATH bootstrap below imports, gate to POSIX
Follow-up to #3850's cherry-pick: keeps the fix but avoids the mid-import
E402 wart and skips the POSIX system dirs on Windows.
2026-07-05 02:05:23 -07:00
Kevin Rajaram
1b7853d7bc fix(gateway): add system dirs to PATH for UV Python compatibility
UV's bundled Python ships a minimal PATH that excludes /bin and /usr/bin,
causing launchctl/systemctl subprocess calls to fail with FileNotFoundError.

Fixes #3849
2026-07-05 02:05:23 -07:00
teknium1
708b57e009 fix(webhook): rate-limit V1 deprecation warning + document V2 signature
- warn once per route instead of on every request (busy senders would
  spam the log)
- document X-Webhook-Signature-V2 / X-Webhook-Timestamp in the webhooks
  user guide

Follow-ups for salvaged #58461.
2026-07-05 01:36:53 -07:00
MorAlekss
70449a4939 fix(security): add timestamp-bound V2 signature for generic webhook replay protection 2026-07-05 01:36:53 -07:00
teknium1
dec4485d2f chore(release): AUTHOR_MAP entries for salvaged PR authors 2026-07-05 00:59:35 -07:00