Sync current Nous Hermes main into Ace patches - #32
Merged
Merged
Conversation
Empty content retries previously fired back-to-back with no delay, wasting up to 3 rapid API calls, and could not be cancelled mid-wait. Apply the same jittered_backoff() already used for rate-limit and API-error retries, sleeping in small increments so a user interrupt aborts the wait instead of blocking until it elapses. Fixes NousResearch#35230
The retry loop gates on real time.time() < sleep_end; with sleep mocked to a no-op the test hot-spun 7.5 wall-clock seconds. Advance a fake clock by each sleep amount instead (pattern precedent: test_session_activity_persist.py).
The existing test only mutated the miss-path return; a mutation to 'return _PROVIDER_LIST_CACHE' (aliasing the global cache) survived the suite. One line pins the cached-return copy. Mutation-checked.
The runtime footer (`/footer`) shows what model ran and how full the context
is, but not how long the turn took. On a messaging platform there is no
progress bar and no shell timer — a turn that took 4 seconds and one that took
four minutes produce visually identical replies. Users comparing models,
providers, or reasoning levels have no at-a-glance signal for the one
dimension they most often care about, and "was that slow or did I imagine it?"
is unanswerable after the fact.
Adds a `latency` field to the existing footer machinery, rendering the
wall-clock duration of the agent run: `<1s`, `22s`, `1m05s`.
`gateway/run.py` measures with `time.monotonic()` immediately around the
`self._run_agent(...)` await in `_handle_message_with_agent` — the same
function that already builds the footer, so the value is the user-perceived
turn duration (monotonic, so it is immune to wall-clock/NTP adjustment).
`latency` is deliberately NOT in `_DEFAULT_FIELDS`. It is opt-in via
`display.runtime_footer.fields`. Every existing footer — and every footer a
user has today without touching config — renders byte-identically.
This is enforced by tests, not just asserted:
- `test_latency_not_in_default_fields` pins the default tuple.
- `test_resolve_footer_config_default_fields_exclude_latency` pins what
config resolution produces for an untouched config.
- `test_default_footer_renders_byte_identically` pins five exact output
strings for default-config renders **while supplying `turn_seconds`** —
proving that even when the caller measures timing, a default-configured
footer does not show it.
- `test_default_build_footer_line_ignores_turn_seconds` asserts
`build_footer_line(...) == build_footer_line(..., turn_seconds=125.0)`
under default fields.
Adding `latency` to `_DEFAULT_FIELDS` fails 11 of these tests.
No new config surface (reuses `display.runtime_footer.fields`), no new env
vars, no new core tool, no new model-facing schema. One new module-private
helper (`_format_latency`), one new keyword argument threaded through the two
existing footer functions, and 3 lines in `gateway/run.py`.
`turn_seconds` defaults to `None` and the field is skipped when it is `None`
or negative, so any call site that does not measure timing keeps working
unchanged.
`tests/gateway/test_runtime_footer.py` (+185): `_format_latency` boundary
table (sub-second, rounding at 59.4/59.6, the `m{:02d}s` zero-pad, 60m), the
render/skip/opt-in matrix, field-order placement, `build_footer_line`
threading, and the byte-stability block above.
RED-proved by mutation — each of these breaks tests:
- `latency` added to `_DEFAULT_FIELDS` → 11 failures
- dropping the `turn_seconds is not None and >= 0` guard → 2 failures
- `{sec:02d}` → `{sec}` → 6 failures
- `build_footer_line` not threading `turn_seconds` → 1 failure
51 passed in `tests/gateway/test_runtime_footer.py`; 54 passed across the
footer blast radius. `ruff check` clean.
Review follow-up on the NousResearch#56798 salvage: the gate shipped fully dormant (no provider profile sets supports_prompt_cache_key, no production caller passes it, and no plain 'openai' profile exists to set it on) — AGENTS.md rejects dead code wired in without E2E proof. Activate the one endpoint where the field is first-class: exact-host api.openai.com (OpenAI documents prompt_cache_key; GPT-5.6+ docs recommend it for cache routing). Deliberately NOT substring matching — Azure/OpenAI-compat endpoints may reject unknown fields and stay opt-in via the flag. 4 new tests (imply + 3 spoof/proxy/Azure negatives); mutation-checked (substring-weakened host check fails the spoof tests).
Re-derivation of PR NousResearch#65645 onto current main: _build_gateway_agent_history already runs inside a turn whose config was loaded once into ctx.user_config; re-reading config from disk via _load_gateway_config() per turn is redundant. Reuse the loaded turn config.
…earch#39267) Re-derivation of PR NousResearch#39399 onto current main: pass --prefer-offline to the web-UI workspace install (both silent and verbose arms of _install_web_deps) and to the update-time Node dependency refresh in _update_node_dependencies, so npm reuses its local cache instead of re-fetching metadata. Test expectations updated to match, mirroring the PR's own test-update commit.
…esearch#33612) Re-derivation of NousResearch#33612 by @LeonSGP43 onto the rewritten scheduler (the original is 10,692 commits behind; its tick() no longer exists in that shape, so this is a fresh minimal fix crediting the PR's idea). The gateway's built-in ticker calls tick(verbose=False) every 60s. The idle early-return was gated on 'verbose and not due_jobs', so idle GATEWAY ticks fell through to load_config() + worker-pool resolution every minute. Return early on ANY idle tick; keep the post-tick MCP orphan sweep (main intentionally reaps orphaned stdio children on idle ticks). 3 new tests; mutation-checked (restoring the verbose-gated guard fails the config-skip test). 66 scheduler tests green.
Salvage of NousResearch#57657, ported onto the plugin layout (the adapter moved from gateway/platforms/feishu.py to plugins/platforms/feishu/adapter.py since the PR's base). lark_oapi takes seconds to import and holds the GIL doing it; the module-level import made every gateway boot pay that cost even with Feishu unconfigured. - _load_lark_oapi() with double-checked locking binds the SDK globals on first use; connect() and _standalone_send() call it via asyncio.to_thread so the loop never blocks on the import. - probe_bot() also calls _load_lark_oapi() (sync context) so the SDK probe path is preserved rather than silently degrading to the HTTP fallback before a first connect. - check_feishu_requirements() is install-only and no longer rebinds globals; test_feishu.py gets a setUpModule that binds them eagerly for tests that inject fake clients. Includes the dedicated lazy-import test file (check-does-not-import, connect-loads-on-worker-thread).
CI exposed the whole class: feishu tests across MANY files (thread routing, text batching, sdk executor, ...) inject a mock _client and skip connect(), so the deferred import leaves the request-builder globals None. Replace the single-file setUpModule with a session-scoped autouse conftest fixture that binds the globals once when lark_oapi is installed; when it isn't, the affected tests already skip via their own skipUnless guards. Full tests/gateway run: zero failures beyond main's pre-existing baseline (sorted failure-diff).
The no-SDK fallback guards check '"Name" in globals()' — correct on main where a failed module-level import leaves those names undefined, but the deferred-import port pre-binds every SDK name to None, so the guard was always true and the fallback paths called .builder() on None (AttributeError) wherever lark_oapi isn't installed. Local runs passed because lark IS installed here; CI's default env has no feishu extra. Rewrote all 14 guards to 'is not None', which is correct under both conditions. Verified by simulating CI with a lark-blocking meta_path hook: 74 passed, 18 skipped (the skipUnless set), zero failures.
chore: add contributor email mapping for WojtekMR3
Two places were using SELECT COUNT(*) when they only needed a boolean: - has_any_sessions() called session_count() > 1 (full table scan) - delete_session() used SELECT COUNT(*) WHERE id=? (full matching scan) Fix: - Add session_count_ge(n) to SessionDB — short-circuits via SELECT 1 FROM sessions LIMIT n, returns bool - has_any_sessions() uses session_count_ge(2) instead of session_count() > 1 - delete_session() uses SELECT 1 ... LIMIT 1 with fetchone() is None - Add tests for session_count_ge
…rchived semantics Review fold-ins on top of NousResearch#56768 (@Skywind5487): - session_count_ge ran its query without self._lock, unlike every sibling counter on SessionDB (session_count, session_count_by_source). - Document the deliberate semantics change: session_count() defaults to archived = 0, which is both the expensive part (full index scan, measured 543us vs 4us on 20k sessions) and wrong for the only caller (has_any_sessions asks 'has this install ever had sessions' -- an archived session is still a created one).
InsightsEngine._get_tool_usage and _get_skill_usage scan messages for role='assistant' AND tool_calls IS NOT NULL, but no index aligns with that predicate, so SQLite scans the full messages table on a large state.db. Add a partial index over exactly those rows. role and tool_calls are base columns in the messages table, so the index lives in SCHEMA_SQL (created on both fresh and existing databases via the executescript on every open) rather than DEFERRED_INDEX_SQL. Adds schema regression coverage (fresh + reopened DB, plan uses the index) and an Insights regression test proving tool/skill output is identical with and without the index present. Fixes NousResearch#67341
Review follow-up (NousResearch#67341): on a freshly initialized state.db (before ANALYZE has run) the source-filtered branches of _get_tool_usage / _get_skill_usage did not select idx_messages_assistant_calls_by_session — the optimizer drove from idx_sessions_source_id and probed each session's messages via idx_messages_session_active, scanning non tool-call rows. Pin the index with INDEXED BY on all four fixed-predicate branches so the plan is deterministic for both the unfiltered and source-filtered scopes without depending on statistics. Safe because the index is declared in SCHEMA_SQL (created by every read-write SessionDB._init_schema) and every InsightsEngine caller opens a read-write SessionDB; read-only attachments (which skip schema init) are never used for insights. Extract the four queries into class constants and add tests: query-plan coverage for both scopes without ANALYZE, row-level equivalence between pinned and un-pinned forms, and an assertion that INDEXED BY fails loudly if the index is absent.
…s absent The INDEXED BY pin is a hard dependency -- SQLite raises 'no such index' when the named index is missing. That happens in production: the web dashboard's usage analytics (_get_usage_analytics, _get_models_analytics) open state.db read_only=True, which skips _init_schema, so a DB last written by a pre-index version has no idx_messages_assistant_calls_by_session and every insights call crashes with OperationalError (reproduced E2E). Probe sqlite_master once in __init__ and strip the pin from the four prepared statements when absent -- identical rows, optimizer-chosen plan, no crash. Replaces the change-detector test that froze the crash as intended behavior with a fallback-equivalence test.
Simplify-pass fold: the four copy-pasted .replace blocks meant a\nfifth pinned statement could forget its strip line — a hard 'no such\nindex' crash on read-only DBs, the exact bug the fallback prevents.\nLoop over the attribute names instead.
list_sessions_rich()'s compression-root projection called _get_session_rich_row() once per root — a separate single-row query per compression root on every session-list render. Resolve every tip id first, then fetch all tip rows in one WHERE id IN (...) query via the new _get_session_rich_rows_batch(). _get_session_rich_row() is now a thin wrapper over the batch method, so the enriched SELECT (preview + last_active) lives in exactly one place — future column changes (e.g. NousResearch#42196's include_system_prompt) only touch one query. get_compression_tip()'s chain walk is untouched; it's a genuine per-session graph walk with branch/delegate-exclusion and race handling, and batching it safely is out of scope here. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds two regression tests for the NousResearch#59077 batch: (1) _get_session_rich_rows_batch(compact_rows=True) uses the schema-derived compact projection (no system_prompt, git_branch/git_repo_root kept); (2) list_sessions_rich(compact_rows=True) threads compact_rows through the compression-tip projection call site. Mutation-checked: hardcoding compact_rows=False at the call site fails test 2.
Simplify-pass fold: SQLITE_MAX_VARIABLE_NUMBER is 999 on pre-3.32\nbuilds (which the repo still supports — the trigram-availability\nmachinery exists for exactly that class), and limit=10000\nlist_sessions_rich callers exist in web_server. Chunk inside the\nbatch helper — the single choke point — so no call site can overflow.
…ach message _dispatch_inbound_event() writes session_key → msg_id/raw_text into _processing_msg_ids and _processing_msg_texts so RecallGuardMiddleware can find and interrupt the currently-processing message. These entries were never removed after a message finished processing, causing both dicts to grow unboundedly — one persistent entry per unique session key for the lifetime of the bot. Fix: clear both entries in the _process_message_background() finally block, after super() returns. The guard compares the stored msg_id against event.message_id before popping: a concurrent pending message may have already overwritten the entry in _dispatch_inbound_event while we were running, in which case the drain task owns it and we must not clear it. When msg_id is absent (nothing was written at dispatch time) the pop is a safe no-op. Note: _msg_content_cache already bounds itself to 200 entries at the same write site; _processing_msg_ids and _processing_msg_texts had no such bound.
_build_msg_body_with_mentions() checks the TTL of each _member_cache entry and returns an empty member list when the entry is stale, but never removes the entry from the dict. Over time every group_code the bot has ever queried accumulates a permanent entry, retaining the full member list (potentially thousands of records per group) until disconnect(). Fix: delete the stale entry at the point it is detected as expired. The next call to get_group_member_list_raw() for the same group will repopulate the cache with fresh data as before. Symmetric with the existing TTL pattern in MessageDeduplicator, which evicts on access.
…regression tests Follow-up on the salvaged pair: the original guard's `not msg_id` arm let an id-less internal/synthetic event erase a tracking entry a concurrently-queued id-bearing message's drain task still needs for recall matching (id-less events never write entries in _dispatch_inbound_event, so they must never pop). Tests cover: normal cleanup, id-less non-erasure, overwritten-entry ownership handoff, TTL eviction + fresh-entry survival.
…-frizikk chore: add frizikk to AUTHOR_MAP
Z.AI has separate billing for general vs coding plans and global vs
China endpoints. On startup, detect_zai_endpoint() probes up to 4
endpoints sequentially with 8s timeout each, taking 8-9 seconds when
the first endpoints return non-200 (rate limited) before a working one
is found.
Replace the sequential loop with concurrent.futures.ThreadPoolExecutor
to probe all 4 endpoints in parallel. Results are returned in
ZAI_ENDPOINTS priority order so the preference chain is preserved.
Benchmark on macOS M4 Max, Python 3.11, Hermes v0.8.0:
Before: 8.8s (sequential: global=0.9s/429, cn=1.6s/429,
coding-global=4.3s/200, coding-cn=2.0s/200)
After: ~4.5s (single round-trip, bounded by slowest endpoint)
Signed-off-by: Merlin <merlin@merlin.me>
react-router v7 exports MemoryRouter from 'react-router', not 'react-router-dom'. The test was written when the repo still imported from 'react-router-dom' (4000+ commits ago).
Sibling site missed by PR NousResearch#54022 — /api/console WebSocket in HermesConsoleModal.tsx has the same buildWsUrl → stale-token → 4401 close path as the PTY and events WebSockets. Without this guard, opening the console after a dashboard restart shows 'Console closed (4401). auth: token_mismatch' with no recovery.
When Grok runs on xAI Responses, only swap to native server-side web_search when the active/configured backend is xai. For Firecrawl and other Hermes providers, keep client dispatch under a renamed wire tool so Grok cannot hijack web_search and ignore user config.
Lock in backend preference, wire-name aliasing, and normalize mapping so configured non-xai search providers stay on the Hermes client path. Also init conflict-recovery generation on the telegram bare-adapter helper so CI polling progress tests do not AttributeError.
Drop the manual web.search_backend / web.backend config-reading block that duplicated _read_config_key in web_search_registry.py. The function now delegates directly to get_active_search_provider() (which reads the same config keys via the registry's canonical resolver) and falls back to _get_search_backend() only when the registry has no providers loaded. Also updates the TestXaiWebSearchBackendPreference tests to monkeypatch the registry instead of load_config_readonly, and adds two new tests for the legacy fallback path (no provider registered -> _get_search_backend).
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
hermes model saves custom_providers models: {default: {context_length}} for
local Ollama. That dict shape was treated as an explicit catalog, so no-key
endpoints skipped live /v1/models probing and Desktop/Telegram only showed
the saved default — Refresh could not help. Keep list/string shapes as
allowlists; pin dict catalogs with discover_models: false.
…urrent-turn-scopes fix(relay): avoid concurrent turn scope corruption
…lay-model-metrics feat(observability): report model and provider usage
Hitting ⌘1 (or cycling ⌃Tab onto the main tab) while Capabilities / Messaging / Artifacts covered the workspace looked dead: the workspace pane was already the zone's active tab behind the page, so fronting it changed nothing on screen. activateTreeTabSlot / cycleTreeTabInFocusedZone now return the activated pane id, and the keybind handlers route back to the loaded session (or the new-chat draft) when the landing pane is the workspace under a full page — the same rule openSession already applies.
…fyNative
Desktop plugins can toast in-app (host.notify) but have no sanctioned way to
reach the OS notification pipeline the app's own approval/turn alerts use, so
a plugin surfacing a genuinely notable background event (e.g. a discovery
plugin finding a match) stays invisible once the user steps away from Hermes.
Add a curated per-plugin door instead of exporting the raw dispatcher:
- ctx.notifyNative({ title, body?, silent? }) on PluginContext — attributed
to the plugin id, routed through dispatchNativeNotification so every
existing gate applies (master + per-kind prefs, post-connect baseline,
away-from-app gating, throttle).
- New 'plugin' native-notification kind with its own Settings ▸ Notifications
toggle (default on), so users silence plugins without losing app alerts.
- New optional `tag` discriminator on the notify payload keys the renderer
throttle and main-process cross-window dedupe per plugin, so two plugins
can't collapse each other's session-less notifications.
Consumer: the Index Network desktop plugin wants background opportunity
alerts; anything in ~/.hermes/desktop-plugins gets the same door.
…gets messageRenderWeight moves out of thread/list.tsx into lib/render-weight.ts. The DOM page budget already spends render cost rather than message count — the store window added next needs the same currency, and one weight function keeps the two layers from drifting apart. No behavior change.
…st (NousResearch#55191) An oversized session rebuilt an unbounded runtime repository on every store update and exhausted the renderer's V8 heap, crash-looping the window. The DOM budget in thread/list.tsx bounds what PAINTS, but every message was still normalized into the repository first, so a session only had to be heavy — not visible — to kill the renderer. selectTranscriptWindow keeps the tail that fits one render-weight page. Weight, not message count: measured against a real 1,175-session store, a 400-message cap disengages on 37 sessions that are heavy but short (one is 133 messages / 1.05MB) while firing on 92 long-but-light sessions that were never at risk. The cut aligns off branch-group boundaries. useRuntimeMessageRepository records a group's fork point the first time it sees the group, so a window starting mid-group would re-parent the surviving branches to whatever happened to precede them. Co-authored-by: HexLab <8422520+HexLab98@users.noreply.github.com>
…rom the store Show earlier spends the already-materialized DOM budget first and only asks the session store for another page once that is exhausted, so the click stays cheap and the store window stays as small as it can be. Paging has no ceiling: each expand grows the window by one budget page until the whole transcript is loaded. Branch persistence stays wired throughout — setMessages is never dropped, so switchToBranch and applyBranchVisibility keep working on a windowed session. Co-authored-by: HexLab <8422520+HexLab98@users.noreply.github.com>
Fold ctx.notifyNative into a ctx.os namespace so every way a plugin reaches outside the app window lives behind one attributed door instead of accreting one top-level ctx method per capability: - ctx.os.notify — the native-notification door from the previous commit, unchanged semantics (plugin kind pref, away-gating, per-plugin throttle). - ctx.os.openExternal / ctx.os.revealPath / ctx.os.writeClipboard — the existing window.hermesDesktop bridge capabilities, now sanctioned and result-shaped: each resolves false (never throws) when the bridge or member is missing, so a plugin branches on the result instead of sniffing the preload surface or crashing on an older shell. No new Electron surface: everything routes through bridge members the app already ships; the notification path keeps every existing gate.
…earch#22622) * fix(credential-pool): clear exhaustion state on key rotation When a user rotates an API key (e.g. via `hermes setup` after hitting a rate limit), _upsert_entry updates the access_token on the existing pool entry but preserves the stale last_status=exhausted from the old key. On the next session the pool finds the entry, sees it exhausted, and returns no usable credentials — even though the new key is valid. Fix: when access_token changes on an existing entry, reset last_status, last_error_code, last_error_reason, last_error_message, and last_error_reset_at. The exhaustion state belongs to the old key, not the new one. * chore: add pasevin@gmail.com to AUTHOR_MAP * fix: clear last_status_at on key rotation, remove unused pytest import Address review feedback from teknium1 on PR NousResearch#22622: - Add last_status_at=None to the reset block (matches all other token-sync reset paths in credential_pool.py) - Assert last_status_at is None in the regression test - Remove unused pytest import flagged by ruff + ty
…eave-pages ⌘1 / ⌃Tab return to the chat from a full-page view
…tions, links, files, and clipboard (NousResearch#78685) * feat(desktop): expose native OS notifications to plugins via ctx.notifyNative Desktop plugins can toast in-app (host.notify) but have no sanctioned way to reach the OS notification pipeline the app's own approval/turn alerts use, so a plugin surfacing a genuinely notable background event (e.g. a discovery plugin finding a match) stays invisible once the user steps away from Hermes. Add a curated per-plugin door instead of exporting the raw dispatcher: - ctx.notifyNative({ title, body?, silent? }) on PluginContext — attributed to the plugin id, routed through dispatchNativeNotification so every existing gate applies (master + per-kind prefs, post-connect baseline, away-from-app gating, throttle). - New 'plugin' native-notification kind with its own Settings ▸ Notifications toggle (default on), so users silence plugins without losing app alerts. - New optional `tag` discriminator on the notify payload keys the renderer throttle and main-process cross-window dedupe per plugin, so two plugins can't collapse each other's session-less notifications. Consumer: the Index Network desktop plugin wants background opportunity alerts; anything in ~/.hermes/desktop-plugins gets the same door. * feat(desktop): ctx.os — the curated OS door for plugins Fold ctx.notifyNative into a ctx.os namespace so every way a plugin reaches outside the app window lives behind one attributed door instead of accreting one top-level ctx method per capability: - ctx.os.notify — the native-notification door from the previous commit, unchanged semantics (plugin kind pref, away-gating, per-plugin throttle). - ctx.os.openExternal / ctx.os.revealPath / ctx.os.writeClipboard — the existing window.hermesDesktop bridge capabilities, now sanctioned and result-shaped: each resolves false (never throws) when the bridge or member is missing, so a plugin branches on the result instead of sniffing the preload surface or crashing on an older shell. No new Electron surface: everything routes through bridge members the app already ships; the notification path keeps every existing gate. --------- Co-authored-by: seref <1573640+serefyarar@users.noreply.github.com>
…NousResearch#67843) * fix(agent): adopt .env credential/base-url edits at the turn boundary A Settings save (desktop PUT /api/env, hermes setup) updates .env and the saving process's os.environ, but a live session worker keeps the base_url/api_key captured at agent init until restart — an open chat silently kept calling the old endpoint (e.g. a local-server key sent to api.openai.com, failing with an opaque 401). Add AIAgent._try_refresh_env_client_credentials(), called at the start of each conversation turn: re-resolve the provider's env-sourced credentials (load_env() is mtime-memoized, so an unchanged file costs one stat()) and rebuild the client via the existing _replace_primary_openai_client machinery when the user edited them. The refresh reacts only to env edits — resolved values changed since the last look — never to mere divergence from the agent's current values: credential-pool rotation and failover legitimately move the session off the env credential, and stomping those back would flap. Config model.base_url / pool custom endpoints keep precedence: edits are only adopted while the session still runs on the registry default or the previously-seen env value. Lift _get_env_prefer_dotenv out of _seed_from_env to module level (get_env_prefer_dotenv) so both the pool seeder and the per-turn refresh share the same .env-over-os.environ resolution, including the op:// indirection handling. Fixes NousResearch#67821 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(agent): address sweeper review on env credential refresh - Cover named custom providers (NousResearch#67935): provider="custom" has no PROVIDER_REGISTRY entry, so resolve the config block's key_env through the same lookup the runtime resolver uses. - Make the edit baseline transactional: a failed client rebuild rolls the agent back and leaves _env_creds_seen un-advanced so the unchanged edit is retried next turn. - Recompute route-derived TLS material and default headers on a base-url change, via a _reapply_route_client_config helper shared with credential-pool rotation so the two paths cannot drift. - Rebase onto main: get_env_prefer_dotenv keeps the scoped _get_secret semantics from the profile-isolation fix (no raw os.environ reads). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: map jskang@lablup.com to rapsealk --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Brooklyn Nicholson <brooklyn.bb.nicholson@gmail.com>
…ound)" (NousResearch#78687) hermes debug share runs on the backend. A desktop app connected to a remote, docker, or SSH backend writes desktop.log on the client machine, so the bundle can never contain it — and the report rendered that as a bare "(file not found)", which reads as "the app logged nothing" and sends triage after a client-side bug it cannot see. Name the writer and the path to collect by hand. Backend-written logs are unchanged, a present desktop.log is still captured, and an empty one still reports "(file empty)" — the app ran and logged nothing is a different fact from the file being on another host.
…script-window fix(desktop): oversized sessions open without crashing the renderer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automated fail-closed upstream sync. Locally verified head: 9866565