Commit graph

1030 commits

Author SHA1 Message Date
Thomas Connally
cb7f6bbb2e feat(agent): track per-model token usage for mid-session model switches
The `sessions` table records only the initial (model, billing_provider)
for a session, so when a user switches models mid-session (via `/model`
or programmatically) every token — including the switched model's — is
attributed to the first model. Insights/billing reports then hide the
cost of the new model entirely (e.g. a session that started on deepseek
and switched to opus shows $0 for opus).

Add a `session_model_usage` table keyed (session_id, model,
billing_provider) that accumulates each per-API-call delta under the
model active at the time of the call. `update_token_counts()` is the
single chokepoint every per-call delta flows through (CLI, gateway,
cron, delegated, codex), so recording there captures accurate
attribution on every platform. Only the incremental path records — the
gateway's `absolute=True` summary overwrite is skipped to avoid
double-counting cumulative totals that can't be split per model. When a
call omits the model, it falls back to the session's recorded model,
matching the existing COALESCE-from-session summary behaviour.

Insights `_compute_model_breakdown` now aggregates tokens and cost from
`session_model_usage`, so a switched session splits correctly across
models, with a defensive fallback to the per-session aggregate for any
session lacking usage rows. A v17 migration backfills one usage row per
existing token-bearing session from its aggregate totals (idempotent via
INSERT OR IGNORE), validated lossless against a 1.3 GB production DB.

Tests: per-model recording, mid-session split, model fallback, absolute
no-double-count, v17 backfill, and an insights-level switch breakdown.

Fixes #51607.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 05:59:42 -07:00
Teknium
a0a6cd80f5
fix(agent): preserve none vs unknown tool effects (#61783)
* fix(agent): persist truthful tool effect dispositions

* fix(agent): preserve successful siblings during orphan recovery

* fix(agent): narrow effect dispositions to none and unknown
2026-07-11 05:41:58 -07:00
Teknium
91d05b982d
fix(xai): recover legacy encrypted replay failures (#62420) 2026-07-11 03:51:38 -07:00
kshitijk4poor
bce17bf6a2 fix(codex): enforce Copilot replay policy at dispatch
Reapply the endpoint-aware preflight after request and execution
middleware so no override can reintroduce a connection-scoped ID.
2026-07-11 12:09:27 +05:30
kshitijk4poor
22d5a35c16 fix(codex): harden Copilot replay classification
Require literal booleans for backend-specific replay policy and pin
non-default status and content preservation through both response paths.
2026-07-11 12:09:27 +05:30
joaomarcos
83b2a685cd fix(codex): also guard the auxiliary Copilot Responses adapter
_CodexCompletionsAdapter (agent/auxiliary_client.py) is a second,
independent producer of Codex Responses input — used by auxiliary
calls (context compression, flush_memories, MoA aggregation,
session_search) that route through CodexAuxiliaryClient instead of
the main agent's ResponsesApiTransport.build_kwargs. It calls
_chat_messages_to_responses_input() directly without is_github_responses,
so the previous commit's fix didn't cover it: an auxiliary call made
against a Copilot-backed session could still replay a connection-scoped
codex_message_items id and hit the same HTTP 401.

Detect the Copilot host from the adapter's own client.base_url (same
check the adapter already does further down for prompt_cache_key
opt-out) and pass is_github_responses through, closing the gap.

Still #32716.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-11 12:09:27 +05:30
joaomarcos
b9146a47bc fix(codex): never replay message-item id on Copilot Responses connections
Copilot (api.githubcopilot.com/responses) binds replayed assistant
codex_message_items ids to a specific backend "connection". Credential-
pool rotation, a gateway restart, or routine load-balancer churn between
turns all invalidate that binding, and Copilot rejects the stale id with
HTTP 401 "input item ID does not belong to this connection" — even for
short ids well under the #27038 64-char length cap, since this is a
connection-scope problem, not a length problem. Once a session captures
one of these ids it is persisted and replayed forever, permanently
bricking the session.

Thread an is_github_responses flag from build_kwargs/convert_messages
into _chat_messages_to_responses_input and drop the id unconditionally
on that path, mirroring how reasoning items already strip id on replay.
phase/status/content are still replayed so cache-relevant signal isn't
lost — only the connection-scoped id is unsafe to reuse.

Written to apply independently of the #27038 length-cap fix so the two
PRs don't block each other; they touch adjacent conditions in the same
block and merge cleanly in either order.

Fixes #32716

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-11 12:09:27 +05:30
kshitijk4poor
02063ece11 fix(cron): scope inline calls to reported transport 2026-07-11 11:07:34 +05:30
kshitijk4poor
47c91e4c34 fix(cron): abort inline requests on timeout 2026-07-11 11:07:34 +05:30
PRATHAMESH75
5c5dd6b7ec fix(agent): run cron LLM calls inline to avoid gateway deadlock (#62151) 2026-07-11 11:07:34 +05:30
joaomarcos
0b2907f586 fix(codex): drop oversized message ids on Responses input replay
Codex assigns assistant message items server-side ids that can run
400+ chars (base64 encrypted blobs), but the Responses API caps
input[].id at 64 chars and rejects the whole request with a
non-retryable HTTP 400. Once a session captures one of these long
ids, every subsequent turn replays it and 400s forever, since the
history persists it in codex_message_items.

Add a 64-char length guard at both replay sites — the history-to-
input converter and the final preflight gate — so oversized ids are
dropped while short ids (msg_...) are kept for prefix-cache hits.
Mirrors the existing pattern for reasoning items, which already
strip their id before replay because store=False means the API
can't resolve ids server-side anyway.

Fixes #27038

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 19:26:08 -07:00
brooklyn!
291eae63b7
Merge pull request #62016 from NousResearch/bb/desktop-vibe-hearts
feat: vibe reactions — floating hearts on affection, across CLI/TUI/desktop
2026-07-10 16:14:27 -05:00
Teknium
b9b463f3bd
feat(security): expose deterministic tool output risk (#61793)
* feat(security): expose deterministic tool output risk

* fix(security): emit output-risk events only for findings
2026-07-10 07:58:12 -07:00
kshitijk4poor
de33c2413b fix(web): harden extract input and display boundaries 2026-07-10 19:14:06 +05:30
liuhao1024
7ae9faecf7 fix(tools): handle dict URLs in web_extract display and tool processing
When web_search results are passed directly to web_extract, the URLs
field contains dict objects (e.g., {"url": "...", "title": "..."})
rather than plain URL strings. Two code paths assumed URLs were always
strings and crashed:

- agent/display.py get_cute_tool_message for web_extract: tried to call
  url.replace() on a dict, causing AttributeError
- tools/web_tools.py web_extract_tool loop: tried regex search on a dict,
  causing TypeError

Both now extract the URL string from dict objects (url or href field) or
fall back to empty string, preserving the cosmetic display and allowing
the tool to process the URLs correctly.

Fixes #61693
2026-07-10 19:14:06 +05:30
kshitijk4poor
97e9c64664 fix(runtime): preserve resolved fork metadata 2026-07-10 18:32:32 +05:30
infinitycrew39
f39c88befb test(curator): assert review fork forwards pool and overrides
Regression test that _run_llm_review passes credential_pool and request_overrides from resolve_runtime_provider into the curator AIAgent fork.
2026-07-10 18:32:32 +05:30
Brooklyn Nicholson
422d9da9bd feat(agent): core affection reaction detector + reaction_callback
Add a token-free, curated affection matcher (agent/reactions.py) — the single
source of truth for detecting user "vibes" (ily / <3 / good bot / heart emoji).
No model call, no tokens. Generalized to return a reaction *kind* so future
reactions can ride the same signal.

Wire an opt-in AIAgent.reaction_callback that fires from build_turn_context on
the incoming user message. It never touches the conversation (cache-safe) and
never fatal — a purely cosmetic side-beat each host can consume.
2026-07-10 05:41:59 -05:00
kshitijk4poor
6abf195682 fix(agent): keep pending verification behind exit provenance
Only restore held verification text when the loop genuinely ends through budget exhaustion. Preserve later interrupts and failures, keep generated-summary fragment explanations, and add regression coverage for both contracts.
2026-07-10 11:53:58 +05:30
kshitijk4poor
8fc80bc2aa test(agent): pin verification fallback edge cases
Cover empty pending output falling back to summarization and a later verified response superseding the held premature report.
2026-07-10 11:53:58 +05:30
kshitijk4poor
1453431881 refactor(agent): scope pending fallback to verification
Name the continuation fallback for its actual verification-only provenance so unrelated continuation paths cannot accidentally inherit its cron-delivery semantics.
2026-07-10 11:53:58 +05:30
kshitijk4poor
cd7c203ab9 fix(agent): preserve gated responses without masking failures
Track held-back verification responses explicitly so budget exhaustion returns the composed report without a second model call. Keep unrelated error and recovery exit reasons intact, preserve Kanban timeout accounting, and cover the real run_conversation paths.
2026-07-10 11:53:58 +05:30
HexLab98
53231fb00b test(cron): cover verify-on-stop iteration-limit exit normalization
Add turn_finalizer regression tests for unknown/budget_exhausted exits
that must normalize to max_iterations_reached for cron delivery.
2026-07-10 11:53:58 +05:30
teknium1
a0972b9748 fix: widen None-deref guards to config-derived sibling sites + tests
Sibling sites of the salvaged #55997 fix, all reading user-editable
config values through .get(key, '').method(): MoA slot provider/model
labels, gateway quick-command alias targets (2 sites), gateway.proxy_url,
and gateway.relay_url. Regression tests for the contributor's two sites
plus the MoA labels.
2026-07-09 21:10:07 -07:00
Teknium
1a47769715
test: deflake CI and dev-machine flaky tests in bulk (11 tests, 10 files) (#61816)
* test: deflake CI and dev-machine flaky tests in bulk

Fixes ten distinct flake sources found by mining recent CI failures and
running the full suite on a dev machine with real user state:

CI-observed races:
- tests/conftest.py live-system guard: allow signal 0 (pure liveness
  probe) through _guarded_kill/_guarded_killpg. psutil.pid_exists()
  probes a just-killed grandchild reparented to init; the subtree check
  fails for it and the guard RuntimeError'd
  test_entire_tree_is_sigkilled_not_just_parent intermittently on
  unrelated PRs.

Hermeticity flakes (fail on dev machines with real state, pass on CI):
- agent/coding_context.py: _marker_root() now skips the shared temp
  root (tempfile.gettempdir()) like it skips $HOME — a stray
  /tmp/package.json flipped every tmp_path test into the coding
  posture (9 failures in test_coding_context.py).
- test_agent_guardrails.py: pin MAX_CONCURRENT_CHILDREN=3 via autouse
  monkeypatch instead of freezing the user's real config value at
  import time (import-time vs call-time config mismatch).
- test_web_tools_config.py: TestCheckWebApiKey now neutralizes the
  ddgs package probe and registry providers — the optional ddgs
  package in a dev venv lit up the fallback backend.
- test_credential_pool.py: block claude_code/hermes-oauth credential
  autodiscovery in the two pool-merge tests that assert exact id
  lists (a real ~/.claude/.credentials.json seeded an extra entry).
- test_modal_sandbox_fixes.py: clear _permanent_approved /
  _session_approved — the user's real command_allowlist silently
  approved the guard-escalation commands under test.
- test_setup_irc.py: stub prompt_checklist to select only the IRC row;
  the non-TTY cancel fallback re-ran the real configured platforms'
  interactive setup_fn, which hit input() under captured stdin.
- test_doctor.py: TestGitHubTokenCheck now patches the module-level
  HERMES_HOME constant (the file's established pattern) instead of
  only setenv — doctor was running PRAGMA integrity_check against the
  real multi-GB state.db and blowing the 300s per-file budget.

Latent atexit-duplication (same _enter_buffered_busy class as #34217):
- test_undo_command.py: drop importlib.reload(tui_gateway.server) in
  fixture teardown; reload re-registers the module's atexit hooks.
- test_session_platform_resolution.py: drop per-test reload of
  tui_gateway.server; every resolver reads env at call time.

* test: sentinel model value in ignore-user-config fallback assertion

With HERMES_IGNORE_USER_CONFIG=1, load_cli_config() falls back to the
repo-root cli-config.yaml (untracked, gitignored). On a dev machine that
file can legitimately set the same popular model the test hardcoded
(anthropic/claude-sonnet-4.6), flipping the != assertion locally while
CI (no cli-config.yaml) stayed green. Use an impossible sentinel model
name instead.
2026-07-09 20:03:11 -07:00
teknium1
881a9520e3 test: regression coverage for null context_lengths key (#47135) 2026-07-09 19:57:43 -07:00
kshitijk4poor
411d599764 test: fold deepseek-v4 cases into canonical reasoning-floor test, drop duplicate file
The salvaged PR added a standalone test_reasoning_timeouts.py that duplicated
the structure of the existing parametrized test_reasoning_stale_timeout_floor.py.
Fold the v4-flash/v4-pro/-free positive cases and deepseek-chat negative cases
into the canonical parametrized tables and remove the redundant file.
2026-07-09 15:04:14 +05:30
liuhao1024
1e16120603 fix(reasoning): add deepseek-v4-flash and deepseek-v4-pro to reasoning timeout floor
DeepSeek V4 models (deepseek-v4-flash, deepseek-v4-pro) emit
reasoning_content in a separate delta field before final content,
requiring the same 600s stale timeout floor as R1. Without this,
streams hang for 30–50s with APITimeoutError on providers like
opencode-go while direct calls succeed in ~3s.

Fixes #60338.
2026-07-09 15:04:14 +05:30
kshitijk4poor
e7648d5912 test: restore gemma-3-27b to keep-extra_content coverage
Review finding: PR #40632's branch had silently dropped gemma-3-27b
from the keep-extra_content test loop (part of its Gemma narrowing,
which this salvage reverts). Restore main's original coverage so a
future narrowing back to Gemini-only fails loudly.
2026-07-09 14:35:14 +05:30
kshitijk4poor
63ddd022a2 refactor(salvage): scope #40632 to the two live copy-on-write sites
Trim the salvaged commit to its two still-valid conversions:
- ChatCompletionsTransport.convert_messages (copy-on-write sanitize)
- QwenProfile.prepare_messages (copy-on-write normalize + cache_control)

Dropped from the original PR:
- agent/prompt_caching.py selective-copy: superseded by #57229 which
  already rewrites apply_anthropic_cache_control on current main.
- Gemma extra_content narrowing (_model_consumes_thought_signature
  'gemini or gemma' -> 'gemini' only) + its two tests: unrelated
  behavior change reverting deliberate e8c3ac2f5; belongs in its own
  PR with its own justification if pursued.

Conflict resolution: preserved main's newer timestamp-stripping
(#47868) inside the copy-on-write path.
2026-07-09 14:35:14 +05:30
pedrommaiaa
724ab9098d perf: avoid broad message prep deepcopies
(cherry picked from commit 030746c560)
2026-07-09 14:35:14 +05:30
Kshitij Kapoor
f556edc10d fix(model_metadata): address Phase-2 review findings on probe caches
Structured review (2a/2b/2c) findings, all fixed:

- MAJOR: detect_local_server_type memo was process-lifetime with no
  invalidation, permanently pinning a URL's server type. Now a bounded
  1h TTL ((type, monotonic) tuples) so a backend swap on the same port
  is re-detected. Test covers ollama->lm-studio swap after expiry.

- MAJOR: legacy disk-row compat was one-way. get_cached_context_length
  and _invalidate_cached_context_length now consult the same key-shape
  set {canonical, literal, canonical+slash} in both directions, so an
  old slashed row is found (and cleared) when the runtime passes the
  normalized URL. Tests pin both migration directions.

- MINOR: _localhost_to_ipv4 did whole-string replacement, which could
  corrupt a proxy URL embedding http://localhost in its query. Now a
  scheme-anchored host-only regex; localhost.example.com and embedded
  substrings pass through. Tests added.

- MINOR: _invalidate_cached_context_length now also drops the
  in-memory TTL probe rows for the pair, so a resolution inside the
  TTL window can't re-persist the value just declared stale.

- Test gaps closed: detect-type cache hit + TTL-expiry re-detection,
  ollama-show TTL expiry re-probe, reverse legacy-row lookups.

- Attribution gate: added zhchl@hermes-agent.local -> 8294 (PR #50572
  author) to AUTHOR_MAP; the strict CI grep needs bare non-plus emails
  literal in release.py.

Gates: ruff clean; targeted suites 202 passed / 0 failed; full
tests/agent 5426 passed with 17 failures identical on clean
upstream/main (pre-existing env-dependent anthropic/bedrock/credpool
tests); mypy delta vs base: 0 new errors; live smoke 6/6 PASS.
2026-07-09 14:08:44 +05:30
Kshitij Kapoor
20cb385328 fix(model_metadata): widen localhost->IPv4 rewrite to all sibling probe sites
#37595 fixed the Windows dual-stack IPv6 timeout only inside
detect_local_server_type. The same 2s-per-probe penalty existed at every
other helper that builds a probe URL from base_url. Extract the rewrite
into _localhost_to_ipv4() and apply it at:

- query_ollama_num_ctx
- query_ollama_supports_vision
- _query_ollama_api_show (server_url derivation)
- _query_local_context_length (server root + LM Studio native URL)

Tests cover the helper's URL forms, non-localhost passthrough, and that
the ollama probes actually POST to 127.0.0.1.
2026-07-09 14:08:44 +05:30
Kshitij Kapoor
040e30aa72 perf(model_metadata): cache ollama /api/show probe + normalize context-cache keys
Follow-up hunks completing the probe-cache cluster:

1. _query_ollama_api_show now goes through the existing
   _LOCAL_CTX_PROBE_CACHE (30s TTL, positive-only, namespaced key) —
   it was the one remaining per-resolution POST not covered by the
   #56431-era wrapper. Failures are never memoized so a server that
   comes up mid-startup is re-probed. Idea credit: #42081 (@Morad37),
   reworked to comply with the positive-only rule.

2. Persistent context-cache keys are normalized through
   _context_cache_key (trailing-slash strip) so http://host/v1 and
   http://host/v1/ share one entry; reads and invalidation honor
   legacy un-normalized rows. Idea credit: #37905 (@stevenau21).

Tests: TTL hit collapses to one POST, failure-not-memoized
(mutation-verified: unconditional caching makes it fail), namespace
no-collision vs the sibling probe, slash-variant dedup, legacy-row
read, dual-shape invalidation.
2026-07-09 14:08:44 +05:30
Rod Boev
9a18a2de12 fix(agent): probe localhost via IPv4 for LM Studio detection
Remaining hunk of the PR-branch fixup commit: the LM Studio first-probe
assertion now expects the IPv4-resolved URL (the code half — applying
the rewrite to `normalized` before deriving lmstudio_url — was folded
into the previous cherry-pick's conflict resolution).

(cherry picked from commit 7d324b0e47)
2026-07-09 14:08:44 +05:30
Rod Boev
91ece5c2fc perf(model-metadata): resolve localhost to IPv4 in detect_local_server_type
On Windows, `localhost` resolves to both ::1 (IPv6) and 127.0.0.1 (IPv4).
httpx tries IPv6 first, hanging 2 sec per probe when the server binds IPv4
only. detect_local_server_type() is called 3+ times during init, each with
a new httpx.Client, compounding to ~14s of dead time.

Replace localhost with 127.0.0.1 inside the function before connecting.
The function is only called for local endpoints (callers guard with
is_local_endpoint()), so IPv6 loopback adds no diagnostic value.

Measured: 19.9s → 4.0s on Windows with a local proxy on 127.0.0.1:8317.
(cherry picked from commit a075d3194b)
2026-07-09 14:08:44 +05:30
kshitijk4poor
55dbc3ffb5 fix(model_metadata): bound the tools-token estimate cache
Follow-up to the salvaged str(tools) fix. The id()-keyed
_TOOLS_TOKENS_CACHE had no eviction, so a long-lived gateway/desktop
backend could accumulate an unbounded number of stale entries as it
builds transient tool lists. Cap it at 256 with oldest-first eviction
(insertion-ordered dict) and add a regression test asserting the cache
never exceeds the cap.
2026-07-09 12:34:11 +05:30
infinitycrew39
f4d5cfd0fd test(model_metadata): cache tools schema token estimate
Adds a regression test that repeated request-token estimates do not re-serialize the same tool schema list.
2026-07-09 12:34:11 +05:30
Kshitij Kapoor
d8bc4f242f fix: serialize /new end→switch boundary on the memory manager worker
Deep review of the cherry-picked #16454 found the ad-hoc flush thread
raced new_session()'s inline on_session_switch(reset=True): memory
providers key off internal _session_id state (MemoryManager.on_session_end
takes no session id), so a late off-thread extraction ran against
post-rotation bindings — misattributing the old transcript to the new
session id, double-ingesting the old turn buffer (supermemory), or
double-committing (openviking already async-finalizes in
on_session_switch).

Redesign: new MemoryManager.commit_session_boundary_async queues
on_session_end + on_session_switch as ONE task on the manager's existing
single-worker background executor (the same worker sync_all already
uses). This preserves the strict end→switch ordering providers depend on,
serializes against per-turn syncs FIFO, keeps /new non-blocking, and
degrades to inline (pre-#16454 behavior) when the executor is
unavailable. No ad-hoc threads; no per-provider changes needed.

The context-engine on_session_end half stays synchronous in
_launch_session_boundary_memory_flush (cheap, must land before
reset_session_state rebinds the engine).

Exit durability: _run_cleanup calls the manager's existing
flush_pending(timeout=10) barrier before shutdown, so '/new then quit'
doesn't drop the queued extraction (shutdown_all's own drain is ~5s and
cancels queued tasks). Bounded well inside the 30s exit watchdog.

Tests: ordering invariant with slow (LLM-like) extraction, FIFO
serialization vs sync_all, switch-fires-even-if-end-raises, no-provider
no-op, CLI snapshot handoff + inline-switch fallback, sync engine
boundary, cleanup flush_pending.
2026-07-09 03:21:54 +05:30
Dixing Xu
e21ba91221 perf(skills): speed up snapshot prompt builds
Fixes #3356

Build the skills snapshot manifest in one directory walk, avoid importing gateway session context during CLI prompt startup, and reuse direct platform-list matching for snapshot entries.

(cherry picked from commit 1a64c2ed04)
2026-07-09 02:41:24 +05:30
Teknium
76381e2a8e
fix(compression): stop compaction thrash — 75% trigger floor under 512K, no summary output cap, reasoning-trace exclusion (#60989)
Sessions on sub-512K-context models were spending most of their wall-clock
re-summarizing: the 50% trigger left too little post-compaction headroom
(the incompressible floor — system prompt, tool schemas, protected tail,
rolling summary — ate most of the reclaimed space), so compaction re-fired
every 1-2 turns. Three compounding defects fixed:

- Threshold floor: models with context windows below 512K now trigger at
  >=75% of the window (raise-only — a higher configured value or per-model
  autoraise like Codex gpt-5.5's 85% always wins). Re-derived on
  update_model() in both directions.
- No max_tokens on the summary call: the summary budget is prompt guidance
  only ("Target ~N tokens"). The wire cap truncated summaries mid-section
  on the Anthropic Messages / NVIDIA NIM paths (thinking models burn the
  cap on reasoning first), yielding truncated or thinking-only summaries
  and compaction loops. Summary token ceiling lowered 12K -> 10K to keep
  the guidance within the intended 1K-10K envelope.
- Reasoning traces excluded end-to-end: inline <think>/<reasoning> blocks
  are now stripped from assistant content before serialization to the
  summarizer, and from the summarizer's own output before the summary is
  stored (previously a thinking summarizer model's trace was persisted in
  _previous_summary and re-fed into every iterative update, compounding
  bloat). Native reasoning fields were already excluded.

Verified E2E with real imports against a temp HERMES_HOME: threshold table
across 64K-1M windows, override interactions (user 0.85 wins, spark 0.70
raised, gpt-5.5 0.85 kept), full compress() round-trip with a thinking
summarizer, and wire-kwargs capture proving no max_tokens is sent.
2026-07-08 11:56:17 -07:00
fyzanshaik
ac6dd598a4 fix(agent): tag desktop chat sessions as desktop
The desktop app's chat panel reuses tui_gateway as its backend, so every chat session was stamped platform="tui". That made the agent read terminal-specific platform guidance while running in the graphical desktop chat surface.

Resolve the misclassification at its source: tui_gateway now picks platform="desktop" when HERMES_DESKTOP=1 and HERMES_DESKTOP_TERMINAL is unset, and keeps platform="tui" for the embedded terminal pane and standalone TUI. Add a PLATFORM_HINTS["desktop"] entry describing the actual chat surface (full GFM markdown, MEDIA: intercept, inline images). Move the embedded-pane clarifier to the platform-hint resolution site so it appends only to the tui hint under HERMES_DESKTOP_TERMINAL=1. Delete the now-dead desktop-hint block from build_environment_hints() that competed with the platform hint.

Standalone TUI sessions produce byte-identical prompts as before; the new desktop hint and clarifier are assembled once per session in the stable tier, so prompt caching is preserved.
2026-07-08 06:18:18 -07:00
Teknium
0e04d14209
feat(sessions): trace export + HF upload via 'sessions export --format trace' (#60507)
* feat(trace): upload sessions to HF Agent Trace Viewer

Salvage trace upload as a smaller CLI-first feature: deterministic Claude Code JSONL export, fail-closed redaction, lazy Hugging Face dependency, and no gateway slash-command wiring.

* chore(trace): drop external porting references from docstrings

Describe the trace-upload design in Hermes' own terms.

* feat(sessions): fold trace upload into 'sessions export --format trace'

Integrates the HF Agent Trace Viewer exporter (PR #36145) onto the
unified export surface instead of a separate 'hermes trace' subcommand:

- --format trace: Claude Code JSONL to stdout/file, or one
  <id>.trace.jsonl per session for filtered bulk export; defaults to
  the most recent session when no --session-id/filters given.
- --upload pushes to the user's private HF traces dataset (--public to
  opt out of private); reads HF_TOKEN with guided setup when missing.
- traces are secret-redacted by default (force mode); --no-redact opts
  out after review; redaction failure blocks export (fail closed).
- hermes_cli/trace.py + subcommands/trace.py removed; agent/trace_upload.py
  is the single engine. Docs EN + zh-Hans; 4 new CLI tests.
2026-07-07 15:12:49 -07:00
kshitijk4poor
b2d6a512d5 fix: normalize string stop + surface dropped stream/tool_choice in Converse shim
Review findings on the salvaged shim: (a) OpenAI callers may pass stop as
a bare string but Converse's stopSequences requires a list — normalize;
(b) call_llm(stream=True) (MoA aggregator) can reach this client and the
shim silently returned a complete response — keep that behavior (the
streaming consumer's got-final-object path downgrades gracefully) but log
it, and log dropped tool_choice, instead of silently ignoring both.
+2 regression tests.

Follow-up to the salvage of #60217 by @xxxigm.
2026-07-08 01:51:05 +05:30
xxxigm
e9da629800 test(bedrock): cover auxiliary Converse routing for non-Claude models
Assert gpt-oss Bedrock IDs resolve to BedrockAuxiliaryClient while Claude
IDs keep the Anthropic SDK path, including async mode.
2026-07-08 01:51:05 +05:30
hmirin
d1c8c03416 feat(agent): add Codex-native compaction paths 2026-07-07 02:39:54 -07:00
kshitijk4poor
144457d801 fix(interrupt): extend post-worker /stop guard to Bedrock streaming path
The salvaged fix added a post-worker _interrupt_requested re-check to the
main OpenAI/Anthropic streaming poll loop. The Bedrock Converse poll loop
(interruptible_streaming_api_call, api_mode='bedrock_converse') has the same
bug class: its worker calls stream_converse_with_callbacks(on_interrupt_check=
...), which breaks out of the event loop on interrupt and returns a PARTIAL
response WITHOUT raising (bedrock_adapter.py). The worker sets result[
'response'] and exits with _interrupt_requested still True, so the in-loop
raise never fires and the poll loop returns the partial — silently swallowing
/stop on Bedrock exactly as it was on the paths the salvaged commit fixed.

Add the identical post-worker re-check before the Bedrock loop's return.
The non-streaming loop (interruptible_api_call) is structurally immune: its
worker's only early return fires off _request_cancelled, which is set by the
main loop immediately before it raises in-loop, so no swallow window exists.

Guard test flips _interrupt_requested True mid-stream (after the pre-flight
check) and asserts InterruptedError is raised; verified RED without the fix
(DID NOT RAISE) and GREEN with it.
2026-07-07 14:54:50 +05:30
isheng
c2c73605e0 test: set pool.provider= on mocks to avoid MagicMock truthy guard trigger
The provider-mismatch guard now checks pool_provider and
current_provider != pool_provider. MagicMock.provider returns
a truthy child mock by default, which would trigger the guard
and skip the pool recovery tests. Set pool.provider='' explicitly.
2026-07-07 14:54:50 +05:30
kshitijk4poor
2c5762f575 chore: debug log for untrusted absolute skill paths; drop misleading test patch
Review findings: (a) an absolute path outside trusted roots passes
through unchanged and gets rejected downstream by skill_view — add a
debug log at the pass-through so the cron 'skill not found' symptom is
diagnosable next time; (b) test_relative_path_unchanged patched
get_skills_dir although the relative branch early-returns before any
root lookup — drop the misleading patch.
2026-07-07 14:40:56 +05:30
kshitijk4poor
713e50e7d2 fix: normalize against tools.skills_tool.SKILLS_DIR, the root skill_view enforces
The extracted normalize_skill_lookup_name() resolved trusted roots via
agent.skill_utils.get_skills_dir(), but skill_view() enforces
tools.skills_tool.SKILLS_DIR — a separate module attribute that callers
and 60+ existing tests patch directly. With the helper reading a
different symbol than the enforcer, any SKILLS_DIR patch (or future
divergence between the two resolvers) makes normalization disagree with
enforcement and absolute-path loads regress silently. Read SKILLS_DIR at
call time (deferred import, cycle-safe) with get_skills_dir() as the
fallback, and align the new tests to patch the enforced symbol.

Follow-up to the salvage of #59829 by @HexLab98.
2026-07-07 14:40:56 +05:30