Commit Graph

28271 Commits

Author SHA1 Message Date
Teknium 93b6cf2ef8 feat(providers): curated picker lists for the Alibaba CN variants
Follow-up to the #77848 salvage: mirror the curated model lists onto
the alibaba-cn / alibaba-coding-plan-cn / alibaba-token-plan-cn
profiles registered in #73345, and add all Alibaba variants to the
qwen provider group so they appear in the drill-down picker.
2026-08-29 18:34:30 -07:00
MumuTW 46076b2d6b feat(providers): curated model list for alibaba-token-plan picker
Token Plan (Personal Edition) model catalog for hermes model /
provider pickers, verified against a live Token Plan subscription
(2026-08-03). Provider profiles landed separately in #73345; this
carries the picker-list half of #77848.

Co-salvaged-from: PR #77848
2026-08-29 18:34:30 -07:00
Teknium 1ee30352ca fix: background review can now read skills before patching — denial storm ended, cache parity intact (#61521, #39996)
The self-improvement review fork advertises the parent's full tool schema
(deliberate — tools[] must stay byte-identical for prompt-cache parity)
but denied everything except memory/skill tools at dispatch. Models
naturally reach for read_file to inspect a SKILL.md before patching, got
denied, then attempted a blind skill_manage patch which the
read-before-write guard correctly refused. One deployment logged ~142
denials + ~204 refusals over 2 days: the self-improvement loop ran
continuously but almost never landed a skill patch.

Fix is dispatch-side ONLY — zero request-body change, cache untouched:

- Whitelist read_file + search_files on the review fork (reads are
  side-effect-free). Write tools (write_file/patch/terminal) stay denied:
  autonomous maintenance must go through skill_manage's validation.
- read_file now registers full reads with the review fork's
  read-before-write guard (same as skill_view), so the natural
  read_file -> skill_manage(patch) sequence lands. Partial reads
  (offset>1 / truncated) don't count. No-op outside review forks.
- Self-correcting deny message: names skill_view/skill_manage/memory as
  substitutes so one denial redirects the model instead of a storm
  (the actionable half of #61521's proposal 2).

Rejects #39997's alternative (narrow the advertised schema on local
endpoints): local backends have KV/prefix caches too, and re-prefilling
a large snapshot is most expensive exactly there.

Live A/B (real dispatch path, isolated HERMES_HOME): on main,
read_file DENIED -> patch REFUSED (read-before-write); on this branch,
read_file OK -> patch LANDED. tools[] identical in both.
2026-08-29 18:33:41 -07:00
Teknium 4d9c6d99ef test(cron): cover bare duration units in parse_duration/parse_schedule 2026-08-29 18:33:19 -07:00
salch-cred 260f007c32 fix(cron): mention bare units in the duration parse error 2026-08-29 18:33:19 -07:00
salch-cred 1d13fe70b9 fix(cron): accept bare duration units like 'hour' in schedule parsing 2026-08-29 18:33:19 -07:00
Teknium bc64ef80be test(a2a): monkeypatched is_deferrable_tool_name accepts the defer_tools positional added in #97979 2026-08-29 18:32:44 -07:00
hermes-seaeye[bot] 105b8650ef fmt(js): npm run fix on merge (#98247)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-30 01:23:27 +00:00
Teknium c1762ff11c test: sweep sibling tests stale on the tool renames + deferral default
The rename sweep in the base commit missed the sibling-test blast radius
(18 red files on CI). Three classes, all fixed:

1. Stale old names in tests (todo/cronjob/process/tour/tip) — updated to
   todo_list/cronjob_manage/process_manage/gui_tour/show_tip at every
   registry.get_entry/dispatch/coerce/preview/allowlist call site, plus
   the coding-brief sentence in agent/coding_context.py now names
   todo_list (and its gating test).
2. Missed rename in production: AGENT_RUNTIME_POST_HOOK_TOOL_NAMES still
   held 'tour' — post-hook ownership would have double-emitted for
   gui_tour via the bridge path.
3. Tests pinning pre-deferral assembly (blank-slate surface, modal
   sandbox resolution, desktop diet, HUD note) now pin their ACTUAL
   contract under the legacy defer:[] override, or assert on granted
   tool names instead of visible schemas.

Also fixes a pre-existing ordering flake surfaced by the sweep:
test_holds_exactly_the_gui_affordances depended on whether an earlier
test had imported apply_layout_tool (registry-registered, not in the
static desktop_ui list) — now forces discovery and pins the full set.

649 tests green locally across all touched files, both orderings.
2026-08-29 18:23:07 -07:00
Teknium ea83ebc1f2 test(desktop): anchor the budget-mirror test paths at the vitest cwd
jsdom's import.meta.url is not a file: URL, so the ported drift tripwire
resolves relay.ts and the two backend mirrors from process.cwd() instead.
2026-08-29 18:17:37 -07:00
Max Hsu 544ad1aa61 fix(desktop): make the relay delivery deadline outlive the backend ceiling
Review follow-up on #93911: the previous constant was set to 1_320_000 ms,
which is exactly the backend's maximum work budget (120s turn-lock wait plus a
600s attempt and its policy-gated re-run) rather than something greater than
it. After those bounded waits the handler still classifies the failure, builds
and runs the retry, serializes the terminal result, unwinds the temp-file and
lock scopes, and returns through the event loop -- so a turn that consumes
nearly the whole budget could still lose the race to the client timer and
resurface #93911 at the upper boundary, with the backend holding a typed
reason while Desktop reported its generic timeout.

The deadline is now composed from the three mirrored backend values plus an
explicit settlement/transport margin, so the arithmetic is visible instead of
being a magic number, and bot-relay-deliver-budget.test.mjs reads
config_defaults.py and methods_bot_relay.py to fail when a mirror drifts or
the margin stops being positive. Nothing in the type system links a JS
constant to a Python default; that test is the seam.

Also adds an adversarial virtual-clock regression: a gateway that answers only
after the full ceiling plus settlement is rejected by a deadline set at the
ceiling and accepted by one with margin.
2026-08-29 18:17:37 -07:00
Max Hsu 10f1c30768 fix(desktop): let bot_relay.deliver outlive the generic 30s request deadline
host.requestProfile() had no way to express a per-call timeout, so every
routed plugin RPC fell to the gateway pool's generic 30s deadline. The
bot_relay.deliver contract is much longer: the backend holds the turn lock
(bot_mode.turn_wait_seconds, default 120s) and then runs a 600s turn, doubled
when the retry policy grants one bounded re-run, so methods_bot_relay.py
documents ~1320s as the bound a client must tolerate. Long turns (Computer
Use, deep research) were therefore killed at 30s and reported back as
unclassified failures rather than the typed reason the backend had classified.

requestGatewayForAgent()/requestGatewayForProfile() already accept timeoutMs;
only the two SDK layers above them dropped it. Thread it through and pass the
documented bound at the bot_relay.deliver call site. The argument is omitted
entirely when unset, so every other caller stays on the pool default.
2026-08-29 18:17:37 -07:00
Teknium 22420eb0d6 docs: delegation.provider alongside base_url carries request overrides (#65035) 2026-08-29 18:16:04 -07:00
Ayush Nangia 5cace31708 fix(delegation): carry provider request_overrides through the base_url path (#65035) 2026-08-29 18:16:04 -07:00
Teknium b6d535dd88 feat(browser): Brave Origin works for real-profile browsing and default-browser detection
Extends the real-profile machinery (PR #95620) to Brave Origin — Brave's
standalone paid build with a fully separate install identity:

- new canonical key 'brave-origin' in _CHROMIUM_BROWSERS
- Windows: BraveOHTML ProgId -> brave-origin; channel ProgIds BraveOBHTML/
  BraveODHTML/BraveOSHTM fail closed (identifiers from brave-core
  install_static)
- macOS: com.brave.Browser.origin bundle id (exact match); .beta/.dev/
  .nightly channel bundles fail closed; /Applications/Brave Origin.app
- Linux: brave-origin.desktop matched BEFORE the bare 'brave' fragment
  (substring scan would otherwise resolve an Origin default to stable
  Brave and drive the wrong profile — #95549 wrong-principal invariant);
  brave-origin-{beta,nightly,dev} fail closed
- profile dirs: BraveSoftware/Brave-Origin on all three OSes (per
  brave-core kProductPathName + Homebrew cask zap paths)
- /browser connect launch tables: Brave Origin split into its OWN group
  so a 'brave' executable lookup can never resolve to the Origin binary
- user-facing strings/docs/desktop tooltip updated

Tests: progid/bundle/desktop map params + data-dir resolution for all
three OSes; 125 passed in the three browser test files.
2026-08-29 18:13:33 -07:00
H-Ali13381 bca0a865c8 fix: detect Brave Origin browsers for CDP connect 2026-08-29 18:13:33 -07:00
Teknium 03e66c8cba polish(tool-search): stub-optimized openers for deferred tools — trigger+verb in the first ~60 chars (the catalog stub is the only ambient hint a deferred tool exists) 2026-08-29 18:13:20 -07:00
Teknium 0b10acc5b8 fix(install): keep install.ps1 pure ASCII — seed the SOUL text with '--' dashes
The synced identity text carries em-dashes, but install.ps1 must stay
pure ASCII (Windows PowerShell 5.1 reads BOM-less .ps1 in the ANSI code
page; a non-ASCII byte in a string literal desyncs the parser — see
tests/test_install_ps1_ascii_only.py, issues #66994/#67000). Seed the
ASCII-dashed variant there instead, and register that variant in
_LEGACY_TEMPLATE_SOULS so Windows installs converge onto the canonical
em-dash text on first run.
2026-08-29 18:10:47 -07:00
nftpoetrist 0610291b5a fix(prompt): sync DEFAULT_SOUL_MD with the #95681 identity rewrite
DEFAULT_AGENT_IDENTITY was rewritten in agent/prompt_builder.py (behavior
spec, exploration-thrift line deliberately removed) but the actual seed
written to disk on first run, hermes_cli/default_soul.py's
DEFAULT_SOUL_MD, was never updated. ensure_hermes_home() writes
DEFAULT_SOUL_MD into SOUL.md on every fresh install before the agent's
first turn, so virtually all real users end up as "SOUL.md users" seeded
with the pre-rewrite text -- including the exact "targeted and efficient
exploration" line the rewrite explicitly banned -- while the new
DEFAULT_AGENT_IDENTITY fallback essentially never serves the "fresh
install" audience its own PR body named as the target.

- DEFAULT_SOUL_MD now matches DEFAULT_AGENT_IDENTITY exactly.
- The pre-rewrite text is added to _LEGACY_TEMPLATE_SOULS so installs
  already seeded with it self-heal via the existing upgrade-in-place
  mechanism (same guarantee as the comment-only scaffold entries: the
  string carries zero user intent, so it's safe to replace).
- Synced the other places install.sh's own comment says "MUST match
  DEFAULT_SOUL_MD": scripts/install.sh, scripts/install.ps1,
  docker/SOUL.md, and the docs/i18n pages that quote the fallback text
  verbatim.
2026-08-29 18:10:47 -07:00
Gille cd1c3211ec chore: map contributor email 2026-08-29 18:10:03 -07:00
AideYu 5cd9c4563c fix(telegram): recover exhausted request pool 2026-08-29 18:10:03 -07:00
Teknium e05c91ac71 fix(cli): slow /handoff transfers no longer misreported as "gateway not running"
Live-reproduced on main: /handoff poll-waited a flat 60s for a TERMINAL
state, but the gateway's dispatch is a full synthetic agent turn (whole
transcript replay + delivery) that routinely exceeds 60s on long sessions.
The CLI then printed "Timed out waiting for the gateway. Is `hermes
gateway` running?" (false diagnosis), called fail_handoff() on the RUNNING
row (stomping the gateway's claim), and promised "Your CLI session is
intact" after switch_session had already re-pointed the session. The
watcher later overwrote failed -> completed: split-brain.

- hermes_state.fail_handoff gains only_states CAS; waiters can only fail
  rows still pending. Owner (gateway watcher) keeps the unconditional form.
- CLI wait loop is two-phase: 60s for the CLAIM (pending) — a timeout
  there really does mean no gateway — then up to 15 min for the claimed
  dispatch with 30s heartbeats; a running row is never failed by the CLI.
- Desktop handoff.fail RPC now CAS-fails pending rows only; a running row
  returns {failed: false, state: running} instead of stomping the claim.

Repro (real _handoff_watcher, real state.db, CLI as separate process,
75s dispatch): before — CLI timeout @60s + false message + row stomped;
after — pending->running@5s->completed@80s, clean CLI exit.
2026-08-29 18:08:55 -07:00
GodsBoy e38cca50d6 fix(memory): keep Mem0 OSS OpenAI requests direct 2026-08-29 18:02:19 -07:00
Ayush Nangia 94aad6dcd2 fix(providers): surface Alibaba China in desktop parity 2026-08-29 18:01:25 -07:00
Ayush Nangia ea129cac54 test(providers): keep Alibaba regression coverage focused 2026-08-29 18:01:25 -07:00
Ayush Nangia 695d86f518 fix(providers): fold Token Plan into the alibaba plugin, add runtime-path regressions, document all variants
Sweeper review, all three points:

- Placement: no new plugins/model-providers/ directory. The Token Plan
  profiles register from the existing alibaba plugin module — one module
  per vendor, matching how the kimi module carries both of its endpoint
  variants. Token Plan is the same vendor/service (Model Studio), same
  OpenAI-compatible protocol, its own key + endpoints; splitting to a
  standalone repo remains a 5-minute change if maintainers prefer.
- Runtime coverage: TestRuntimeAlibabaRegionalAndTokenPlan exercises
  resolve_runtime_provider() for all four variants — provider, api_key,
  api_mode, base_url — alongside the existing zai/minimax/kilocode
  runtime regressions.
- Docs: providers.md, environment-variables.md, cli-commands.md updated
  with the bundled variants and their env keys/base-url overrides.
2026-08-29 18:01:25 -07:00
Ayush Nangia 7cf7df0297 fix(providers): register Alibaba China + Token Plan provider profiles (#73265)
The models.dev catalog advertises alibaba-cn, alibaba-coding-plan-cn, and
alibaba-token-plan(-cn), and resolve_provider_full()'s catalog chain lets
the CLI --provider path resolve them — but auth.resolve_provider() (the
credential/runtime path used by 'hermes chat') consults only
PROVIDER_REGISTRY and raised "Unknown provider 'alibaba-coding-plan-cn'"
(hermes_cli/auth.py:1937). PROVIDER_REGISTRY auto-extends from provider
profiles (auth.py:461-490), so the fix registers the missing profiles at
that chokepoint: alibaba-cn joins the alibaba plugin, alibaba-coding-plan-cn
joins alibaba-coding-plan, and a new alibaba-token-plan plugin registers
both regional token-plan tiers. Names match the catalog keys exactly.

No core edits — plugins/model-providers is the designed extension path.
2026-08-29 18:01:25 -07:00
nftpoetrist d6a6d87c4a fix(tools): restore setup_mcp's never-hand-edit instruction
9d9f44d638 removed the desktop platform hint's "never hand-edit
mcp_servers config for them" sentence, reasoning it was a "word-for-word
duplicate of the setup_mcp tool schema... taught on every call." The
schema has never contained that instruction — only "never re-ask after
a decline." setup_mcp is desktop_ui-toolset-only and no runtime guard
in agent/file_safety.py covers mcp_servers config, so removing the only
place teaching this left a real gap: a model asked to add/configure an
MCP server could just write_file into mcp_servers config directly,
bypassing the consent-card/OAuth flow the tool exists to enforce.

Restored the instruction directly in SETUP_MCP_SCHEMA's description —
completing the original commit's stated intent (move it to the schema)
rather than reverting to the platform hint, since the schema reaches
every setup_mcp call regardless of platform hint wording changes.

Added a regression test asserting the schema description forbids
hand-editing mcp_servers config, so a future prompt-diet pass can't
silently drop it again without a test failing.
2026-08-29 17:58:51 -07:00
nftpoetrist bc81d0a665 docs: sync stale /background references with the /bg + /btw split
74a95a3ddf promoted /bg and /btw to independent canonical commands and
retired /background entirely (hermes_cli/commands.py's COMMAND_REGISTRY
has no "background" command or alias). Three places still taught the
old name:

- skills/autonomous-ai-agents/hermes-agent/references/slash-commands.md:
  the bundled hermes-agent skill's own slash-command reference — the
  skill's SKILL.md explicitly routes the model here for in-session
  command questions, so a model following it would emit the dead
  `/background <prompt>` and never learn /btw exists.
- ui-tui/README.md: listed /btw as an alias of /background, which is
  simply wrong post-split (both are independent, alias-free commands).
- tests/cli/test_cli_background_status_indicator.py: docstring/comments
  described the ▶ indicator by the retired command name.

No behavior change; corrects documentation only.
2026-08-29 17:58:46 -07:00
nftpoetrist 8142494401 fix(tui): render nested todo subtasks via the parent field
CLI, ACP, and the desktop app all got nested-subtask rendering (the
optional `parent` field on a todo item), but the TUI never did. Its
TodoItem type had no `parent` field, parseTodos() in turnController.ts
dropped it even if the tool payload sent it, and TodoPanel rendered the
list with a flat map() and a single fixed indent — a session using
nested subtasks showed every subtask at the same visual level as its
parent, with no hierarchy cue, in the terminal UI.

- types.ts: add the optional `parent` field to TodoItem, matching
  apps/desktop/src/lib/todos.ts's TodoItem exactly.
- turnController.ts: parseTodos() now preserves parent (trimmed,
  dropped if empty or self-referential), the same normalization
  desktop's parseArray() applies.
- lib/todo.ts: port todoTree() from apps/desktop/src/lib/todos.ts
  verbatim — same DFS-with-depth algorithm, same dangling/cycle
  handling, so both surfaces render identical hierarchy from the same
  `parent` field.
- todoPanel.tsx: render todoTree(todos) instead of a flat map(), with
  per-row indentation scaled by depth (capped at 4 levels, mirroring
  desktop's status-row.tsx cap).
2026-08-29 17:58:41 -07:00
Teknium b1a46e192c fix(tool-search): pull clarify back out of the default defer set — A/B showed structured ask collapses when deferred
Maintainer A/B (288 live runs, 3 model tiers, results in the PR body):
with the clarify schema visible, models used structured ask-the-user
18/18 on ambiguous tasks (score 1.00 all models). Deferred, usage
collapsed to 7/18 (gpt-terra 0/6) — models still asked, but as
plain-text turn-ending questions: no structured choices, no recommended
option, an extra user round-trip. The ask-the-user affordance has to be
ambient to fire; a catalog stub is not enough (~250 tok to keep eager).

- _DEFAULT_DEFERRED_TOOLS: remove clarify (19 -> 18 deferred)
- regression test pins clarify ∉ default defer set AND assembles direct
  while the bridge is active (sabotage-verified: fails with clarify
  in the set)
2026-08-29 17:58:40 -07:00
Teknium 1a47a36422 feat(loop): first wakeup fires immediately by default
Flip the salvaged --start-now behavior (PR #97958) into the unconditional
default: /loop's first iteration is due the moment the loop is set, then
recurs on the normal cadence. The flag is dropped — it was never released,
so there is nothing to deprecate.

- LoopManager.set(): next_due_at = now for both cadence modes
- drop --start-now parsing, the persisted LoopState.start_now field, and
  the flag from help text; confirmation now always says the first wakeup
  fires now
- tests updated to pin the new default (incl. the TUI not-due test, which
  now has to push next_due_at out explicitly)
- docs: quick-start and command table describe the immediate first run
2026-08-29 17:43:38 -07:00
oxngon 796babaaed feat(loop): add --start-now to fire the first wakeup immediately
/loop [interval] <prompt> currently schedules the first wakeup one full
interval after the command runs (next_due_at = now + interval). When the
user just told Hermes what to check, waiting the whole interval before
any output feels like the command was ignored.

Add an opt-in --start-now flag that keeps Claude Code parity as the
default but lets the user run the first iteration immediately, then
continue on the cadence:

  /loop 1h check the deploy status            # first run in 1h (unchanged)
  /loop 1h --start-now check the deploy       # first run now, then hourly

- parse_loop_args(): parse and strip --start-now (leading or trailing)
- LoopState: new persisted start_now field (default False, survives
  serialization round-trip and old rows missing the field)
- LoopManager.set(): next_due_at = now when start_now, for both fixed
  interval and self-paced modes
- dispatch_loop_command(): wire start_now through, update help text, and
  report "First wakeup fires now" in the confirmation
- website/docs: document the flag in the /loop guide
- tests: parse (trailing/leading/absent/self-paced/combo/prompt-word),
  tick lifecycle (due immediately vs after interval), serde round-trip,
  and dispatch-level confirmation
2026-08-29 17:43:38 -07:00
kshitijk4poor 4209d371aa refactor(models): reuse _extract_model_name in the Portal-recommendation validation tier
The inline set-comprehension re-implemented modelName extraction that
_extract_model_name() already provides (and that both
union_with_portal_free/paid_recommendations already use). Beyond the
duplication, the inline str(entry.get("modelName", "")) stringified
non-string values — a malformed Portal entry with modelName 5 would have
produced a garbage "5" match that discard("") does not filter. The helper
isinstance-checks and returns None for those, so routing through it makes
the validation tier semantically identical to the union helpers.

Adds test_non_string_model_name_entries_ignored locking the behavior
(mutation-checked: fails on the raw-stringify form, passes on the helper).
2026-08-29 22:41:55 +05:30
ygd58 0ffad55e09 fix(models): accept live Nous Portal recommendations in /model validation
Fixes #71312 (duplicate #71313).

When selecting a model via the Telegram /model picker (or any other
messaging-platform slash command, since they all share
validate_requested_model() through gateway/slash_commands.py ->
model_switch.switch_model()), a model available via Nous Portal's live
/api/nous/recommended-models endpoint but not yet in the hardcoded
curated catalog (_PROVIDER_MODELS["nous"]) was rejected with "was not
found in this provider's model listing" -- even though the exact same
model works fine via `hermes chat -m <model> --provider nous`.

Root cause: `hermes chat` merges Portal recommendations into its model
list via union_with_portal_free_recommendations() /
union_with_portal_paid_recommendations() at model-list build time
(hermes_cli/auth.py, web_server.py, model_setup_flows.py,
model_switch.py), so the model already appears "known" by the time
validation runs for that path. validate_requested_model() itself,
which every per-message /model command goes through, only checked the
live /v1/models listing and the curated catalog (_model_in_provider_catalog) --
never the Portal recommendations feed -- so a model that exists only
in Portal Recommendations was rejected on that path specifically.

Fix: add a Nous-specific fallback tier in validate_requested_model(),
checked after the curated-catalog fallback and before the final
rejection, reading the same fetch_nous_recommended_models() feed
(free + paid tiers) the CLI union helpers already use. Scoped to
provider == "nous" only; short-circuits before the network call when
an earlier tier already accepted the model; fails closed (rejects,
doesn't crash) if the Portal feed is unreachable.

Reported two issues filed 3 minutes apart with identical content by
the same author (#71312, #71313) -- commented on #71313 marking it a
duplicate of #71312 and pointing to this fix (could not close it
directly, no admin rights on the repo from this token).

6/6 new tests pass in TestValidateRequestedModelNousPortalRecommendations;
95/95 in the full tests/hermes_cli/test_model_validation.py file;
87/87 in tests/hermes_cli/test_models.py (unaffected, confirmed).
2026-08-29 22:41:55 +05:30
Hyusein Leshov 835a913ffd fix(compression): arm the failure cooldown when codex compaction fails
Closes #75364.

`_compress_context_via_codex_app_server` returns the transcript unchanged
when the codex thread reports `interrupted` or `error`. The session is
therefore still above threshold, and nothing records that the attempt
failed — so the next turn retries immediately, and keeps retrying for as
long as the condition persists.

Every other compression path arms the shared failure cooldown, records an
ineffective-compression strike, or both. This path records neither:

* `_hygiene_compression_failure_cooldowns` is set only on
  `asyncio.TimeoutError`, or behind `_last_compress_aborted`, which is
  assigned exclusively in `context_compressor.py` on the Hermes summarizer
  path.
* `compression_ineffective_count` lives in `ContextCompressor`, and this
  path returns before any compressor bookkeeping runs.

`compress_context` already documents the rule this path was missing —
"Every automatic entrypoint must honor compressor-owned cooldown and
breaker state" — but the codex branch dispatches above that block and
returns from inside it.

`result.interrupted` needs no unusual configuration to occur: an ordinary
user message arriving mid-compaction sets it (see
`codex_app_server_session.py`, which produces the "compact turn
interrupted" string). Observed in production on a Discord gateway session
at ~315k tokens against a 258k window, where compaction was attempted on
essentially every turn for ~70 minutes; the session's
`compression_ineffective_count` was still 0 afterwards.

This reuses the existing cooldown rather than adding a new mechanism:

* arm `_record_compression_failure_cooldown` with the existing
  `_SUMMARY_FAILURE_COOLDOWN_SECONDS` when compaction returns
  interrupted/error;
* honor an active cooldown on entry, matching the Hermes path.

`force=True` bypasses both, so an explicit /compress is never braked by a
failure it did not cause, and a successful compaction arms nothing.
2026-08-29 22:29:28 +05:30
kshitijk4poor 1e21fe8624 test(custom): align Mistral-omission test inputs; soften models.py comment
The chat_completions and transport-parity Mistral tests pinned the
same branch with different reasoning_config shapes ({effort:none} vs
{enabled:False, effort:none}) — behaviorally identical since effort
short-circuits first, but the drift reads as a semantic difference.
Align both to the explicit form. Also scope the port-guard comment to
the try/except shape it actually shares with hermes_cli/models.py.
2026-08-29 21:58:00 +05:30
kshitijk4poor c870589831 fix(custom): tolerate malformed ports in the Ollama URL heuristic
urlparse raises ValueError on non-integer / out-of-range ports, and
http://myhost:99999/v1 passes OpenAI-client construction (only httpx
rejects it later), so the crash was reachable from build_kwargs on
every request for such a URL. Wrap the parsed.port check in the same
try/except ValueError guard hermes_cli.models already uses around its
11434 check, and pin it with parametrized tests.
2026-08-29 21:58:00 +05:30
xxxigm 6ba8308309 test(custom): pin think=false to Ollama URLs, omit it for Mistral
Cover the Mistral extra_forbidden case and keep the Ollama dual-emission
contract (think=false + reasoning_effort=none) on port 11434 / ollama hosts.
2026-08-29 21:58:00 +05:30
xxxigm 31f0336da7 fix(custom): omit Ollama-only think=false on strict OpenAI-compat endpoints
reasoning_effort: none was injecting extra_body.think=false for every
custom provider. Mistral (and other extra=forbid hosts) reject that
field with HTTP 422. Keep think=false on Ollama URLs only; still send
top-level reasoning_effort=none so /v1 thinking-off keeps working.
2026-08-29 21:58:00 +05:30
Victor Kyriazakos 3f36c87e1e feat(relay): delete_message over the additive delete op — fresh-final preview cleanup
Companion to the connector's delete op (gateway-gateway 119a228). The
fresh-final unfurl route re-posts the completed reply and previously left
the sealed streamed preview behind (double delivery). delete_message now
emits op=delete when the negotiated descriptor advertises it; without the
advertisement it returns False with zero wire traffic, degrading to the
old leave-the-preview behavior against older connectors.

Consumer-level test drives placeholder -> stamped fresh final -> delete
of the original preview id.
2026-08-29 08:29:15 -07:00
Victor Kyriazakos cef4c88f71 fix(relay): route force-on-unfurl streamed finals through fresh chat.postMessage
Slack evaluates link previews exactly once, at chat.postMessage (live
probe 2026-08-28: URL at post + stamps unfurls; a chat.update that
INTRODUCES the URL never does, stamped or not). Edit-based streaming
posts its first frame before the model produces any URL — on flat DMs
with tool_progress=accumulate that frame is the task card — so a
configured unfurl_links/media: true could never surface a preview:
the only post Slack evaluates carries no link.

RelayAdapter now implements prefers_fresh_final_streaming(): True only
when the Slack unfurl hints contain an explicit True AND the final text
carries a link. The stream consumer then delivers the completed reply
as one fresh send — URL and stamps present at the single moment Slack
looks. False-only hints (enterprise fail-closed posture) keep the edit
lane untouched: suppression rides the placeholder post and edits can
never add a preview, so false inherits with zero streaming-UX cost.

Consumer-level contract test drives the exact regression shape
(placeholder frame -> URL-bearing final) and asserts op=send + stamps;
verified RED against the unfixed adapter, GREEN with the hook.
2026-08-29 08:29:15 -07:00
Teknium e16ad33a9d feat(tool-search): core-tool deferral — curated 19-tool set behind the bridge by default; renames todo_list/cronjob_manage/process_manage/gui_tour/show_tip with legacy aliases (13.4K -> 6.9K desktop schemas, -49%) 2026-08-29 08:26:24 -07:00
Teknium 578f85cfb0 feat: /btw rides the background-review cache-parity fork for full-context answers
The initial /btw implementation (#97937) answered from a rendered
plain-text transcript digest — truncated context, cold-written tokens on
every question. Teknium's call: reuse the self-improvement review fork
instead, which keeps the entire prompt cache stable for the fork and
gives it the complete conversation for very cheap.

- agent/background_review.py: extract the review-fork construction into
  build_cache_parity_fork() — same runtime/credentials as the parent,
  byte-identical system prompt / tools[] / reasoning config on the
  same-model path, shared session_id for prefix warmth, full persistence
  detachment (no state.db writes, no rotation, no external memory,
  in-place-only compaction). The review thread now calls the helper;
  behavior unchanged (full review test suite green).
- agent/side_question.py: /btw prefers the fork when a live parent
  AIAgent exists — replays the untruncated snapshot as warm cache reads,
  denies every tool at dispatch via an empty thread whitelist (tools[]
  stays byte-identical for cache parity), attributes usage to the parent,
  and trims a mid-turn snapshot tail so role alternation holds. The
  one-shot digest remains as fallback (no live agent = cold cache anyway,
  and any fork failure degrades gracefully).
- CLI passes self.agent, TUI passes the session agent, gateway looks up
  the chat's cached agent (parity with how turns reuse it).

Live-verified: /btw on the worktree runs the fork path (agent.log shows
the side question as a forked conversation turn on the parent session_id
with the full history replayed), answers correctly from context.
2026-08-29 08:23:47 -07:00
kshitijk4poor 360761c8cf docs: surface Tencent TokenPlan + hy4-preview across provider docs
Follow-up for salvaged PR #96939 (docs completeness audit): setup
table, quick commands (TokenHub example refreshed to hy4-preview),
fallback tables, env-var reference, quickstart, --provider choices,
and .env.example (TokenHub block was missing there too).
2026-08-29 20:51:17 +05:30
kshitijk4poor ac5c8f58db fix: drop duplicate hy4-preview context entry — main's 1_048_576 wins
Follow-up for salvaged PR #96939: main already added hy4-preview at
1_048_576 (f7c79efbac); the cherry-picked duplicate key later in the
dict silently overrode it with 1024000.
2026-08-29 20:51:17 +05:30
simonweng e74e594a86 fix:update test info 2026-08-29 20:51:17 +05:30
simonweng 0fb5cab0d4 feat:add hy4-preview model and tokenplan provider 2026-08-29 20:51:17 +05:30
kshitijk4poor b954547e72 fix(nebius): route effort through canonical clamp_effort — hand-rolled map inverted the ladder
Review finding on salvaged #28253: the hand-rolled mapping sent
ultra -> medium while xhigh -> high (stronger request, weaker wire
value). Declare NEBIUS_EFFORTS in agent/reasoning_effort.py and use
clamp_effort like the zai/kimi/tokenhub call sites; disable detection
stays ahead of the clamp since clamp_effort('none', ...) returns the
floor, not off. Adds a monotonicity regression test.
2026-08-29 20:39:44 +05:30
kshitijk4poor a8e11d21c0 docs: surface Nebius Token Factory across user-facing provider docs
Follow-up for salvaged PR #28253 (docs completeness audit): setup
table, quick command, fallback tables, env-var reference, quickstart,
--provider choices, and .env.example.
2026-08-29 20:39:44 +05:30