Conversation
On dashboard-only sessions nothing else executes check_fn warmers (they live only in the tool-schema build), so the hub's read-only cache lookup would report auth_required=False forever. On a cache miss, schedule a deduplicated daemon-thread probe off the request path; the short hub TTL surfaces the verdict on the next fetch.
Follow-up to NousResearch#77652: each runFlush registered a fresh requestAnimationFrame and never cancelled it. Chromium parks rAF callbacks for hidden renderers, so a long hidden stream at the 33ms floor accumulates thousands of parked closures that all fire in the first frame on refocus (all but one no-oping through the stale-frame guard). Track the pending handle, cancel it before requesting a new one (only the newest flush's measurement matters), and cancel on unmount.
…plify-pass) The 2-line alias had zero production consumers (web_server calls get_usage_breakdown directly). Tests rewired onto the real API; the contracts they pin are unchanged. Stale test docstring fixed.
fix(desktop): keep a mid-turn reply on screen when its session is reopened
…esearch#39200 + NousResearch#74778 salvage) Re-derivation of aydnOktay's twin clamp PRs onto current main (the session-list endpoints moved into web_routers/; the analytics endpoints gained asyncio.to_thread wrappers since the originals): - limit le=100 on /api/sessions, /api/sessions/search and the /api/profiles/sessions fan-out (one unbounded request could drag every session row + correlated-subquery preview work out of SQLite, times every profile's state.db on the fan-out). - days ge=1 le=365 on /api/analytics/usage + /api/analytics/models (huge or non-positive values force full-history InsightsEngine work or inverted windows; the UI only offers 7/30/90 presets). FastAPI Query bounds reject at the validation layer (422). 8 new tests; both clamp classes mutation-checked (clamp removed -> its tests fail).
…ding) le=100 would 422 real desktop callers: sessions-settings fetches archived at limit=200, the command palette lists at 200, and the electron remote-merge over-fetches limit+offset (exceeds 100 at offset>=81, and its .catch(()=>null) silently drops remote sessions). Clamp must sit above real client maxima. New test pins limit=200 w/ offset.
…nt render - Plugin manifests are now cached in sessionStorage on fetch. - On refresh, plugin routes are registered synchronously from cache, preventing unwanted redirects to /sessions. - Removes the !pluginsLoading guard from the catch-all route in App.tsx, as plugin routes are now always available on first render. - Background fetch always updates the cache and routes, so new/removed plugins are reflected after reload. - Resolves the race condition where plugin pages would redirect to /sessions on hard refresh.
… override The sessionStorage seed set loading=false whenever any cache existed, which defeats App.tsx's load-bearing pluginsLoading gate: with a cached manifest that declares tab.override === "/chat", the persistent ChatPage host must NOT mount before plugins resolve, or it spawns a PTY and gets yanked when the override plugin takes over the route. Seed loading=false from the cache only when no cached manifest overrides /chat (canSeedLoadedFromCache); manifests are still seeded either way so plugin routes register synchronously on refresh. Adds focused tests for the gate, including the /chat-override case.
…aders Every hashed bundle chunk under /assets/ was served with no caching directives, so each dashboard load re-fetched (or at best revalidated) every JS/CSS chunk. Those filenames carry a Vite content hash — the bytes behind a given URL can never change; a rebuild mints new filenames referenced by a freshly served index.html. Mark them Cache-Control: public, max-age=31536000, immutable: - the /assets StaticFiles mount, via a subclass that stamps the header on 200s only (404s stay uncached — a rebuild can create the file), - serve_css, preserving its X-Forwarded-Prefix url() rewrites for /fonts/, /fonts-terminal/, /ds-assets/, /assets/. index.html keeps no-store, no-cache, must-revalidate — it is the mutable entry point that binds users to the current hashes. The original PR also added hand-rolled per-request gzip compression of asset responses; that part is deliberately dropped. This server is a localhost-default dashboard backend: compressing every response on the CPU to save loopback bandwidth is a pessimization, and callers that front it with a real proxy already get compression there. Salvaged from PR NousResearch#28543 (idea by @sea-monsters; gzip groups dropped as described above). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…imit window resets restore_primary_runtime retries the primary every turn once the 60s transient cooldown clears. For subscription-window limits (Claude Pro/Max 5h windows, Codex weekly caps) the reset is hours or days away, so every retry is a guaranteed failure costing two provider switches and two prompt-cache invalidations per turn. Add CredentialPool.next_available_at() (earliest reset across exhausted entries; None when available now or no reset info) and gate the restore on it: skip while the primary's pool says nobody can serve, restore on the first turn after the reset elapses. Fail-open: any gate error or missing reset info falls through to the existing per-turn retry, so recovery can never be later than today. Cross-provider fallbacks consult the PRIMARY's pool (not the attached fallback pool), reusing the loaded pool for the existing rebind to keep auth reads at one per restore.
Review fold on the NousResearch#67642 salvage: next_available_at() called _available_entries() — which prunes DEAD entries, syncs tokens, and persists — and iterated self._entries with no lock, racing concurrent select()/rotation exactly as has_available()'s comment warns. Wrap the method body in self._lock and pin it with a non-blocking-acquire probe test.
…g lock select() and acquire_lease() held self._lock during the entire _available_entries() loop, which for openai-codex and xai-oauth providers includes a cross-process file lock (_auth_store_lock) plus OAuth token refresh HTTP POST. The lock timeout can exceed 20 seconds, blocking all credential pool consumers across every gateway thread and subagent. Collect single-use-token refresh entries under the lock, then execute the refreshes outside it. On success the refreshed entry is merged back into the pool and re-selected. Non-single-use providers (anthropic, nous) continue refreshing inside the lock since their refresh is a simple HTTP POST with no cross-process coordination.
Review folds on the NousResearch#71775 salvage (dossier findings 1+2): - self._lock becomes an RLock and the mutation primitives (_replace_entry, _persist) are now self-locking, so the deferred single-use-token refresh path — which deliberately runs its cross-process flock + OAuth network I/O OUTSIDE the pool lock — still serializes its pool mutations against concurrent select()/rotation. In-lock callers re-acquire reentrantly. - Dropped _refresh_pending_entries' redundant second _replace_entry: _refresh_entry already merges the refreshed entry internally. Adds tests/agent/test_credential_pool_deferred_refresh.py pinning both invariants: select() must NOT hold the lock during the refresh window (the PR's whole point), and the post-refresh mutations MUST contend on the lock (blocking-thread probe).
```
Error: optimization failed: no such table: messages_fts_trigram
No data was lost. Re-run to resume.
```
on any install where the trigram FTS index is legitimately absent. The failure is
deterministic — re-running can never make progress, because the crash happens at the same
point every time — so the database is permanently stuck on the legacy high-footprint FTS
layout with no supported way forward.
Observed on a 5.4 GB production `state.db`. After the fix the same database optimized
successfully and shrank to 3.3 GB.
The trigram index is absent whenever the runtime cannot maintain it. On a SQLite build
without the `trigram` tokenizer, `_ensure_fts_schema()` returns `False`, so `__init__`
leaves `self._trigram_available = False` and no `messages_fts_trigram` table on disk. This
is a **supported degraded runtime**, not damage — CJK/substring search falls back to
`LIKE` and everything else works normally. `_is_fts5_unavailable_error()` and
`_warn_trigram_unavailable()` exist specifically to make this path graceful.
Two code paths write the boundary sweep for the deferred FTS rebuild, and only one of them
respects that flag:
| Function | Trigram `INSERT` guarded? |
|---|---|
| `fts_rebuild_step()` | ✅ `if include_trigram:` where `include_trigram = self._trigram_available` |
| `_fts_rebuild_finish()` | ❌ unconditional |
`_fts_rebuild_finish()` runs the boundary sweep at the *end* of the backfill. Its
unguarded `INSERT INTO messages_fts_trigram …` raises `OperationalError`, which propagates
out of `optimize_fts_storage()` and aborts the entire optimization — *after* the backfill
has already completed. Hence the characteristic output showing 100% progress immediately
before the error:
```
Rebuilding index: 100% (909,671/909,671)
Error: optimization failed: no such table: messages_fts_trigram
```
There is a second, quieter consequence. The teardown phase that reclaims the demoted
`fts_v22_trash_*` shadow tables runs *after* the backfill phase in
`optimize_fts_storage()`. Because the crash happens before teardown is ever reached, those
tables are never emptied or dropped — so the space the migration was supposed to reclaim
stays allocated indefinitely, and the leftover trash tables look (misleadingly) like
evidence of a half-finished migration.
Build a populated v23 database, set the deferred-rebuild markers, then reopen it on a
runtime where `_ensure_fts_schema('messages_fts_trigram', …)` returns `False` (exactly
what a SQLite build without the trigram tokenizer produces) and call
`optimize_fts_storage()`:
```
[precondition] trigram absent, _trigram_available=False, rebuild pending ✓
RED ✗ optimize_fts_storage raised OperationalError: no such table: messages_fts_trigram
```
With this patch applied, unchanged harness:
```
optimize_fts_storage returned {'ok': True, 'vacuumed': None}
GREEN ✓ optimize ok; markers cleared; base FTS 'zebra' -> 200 hits
```
Full harness and transcripts in `TEST-EVIDENCE.md`.
Gate the sweep on `self._trigram_available`, exactly as `fts_rebuild_step()` already does:
```python
include_trigram = self._trigram_available
def _do(conn):
...
if include_trigram:
conn.execute("INSERT INTO messages_fts_trigram(...) ...")
```
The base `messages_fts` sweep and the marker cleanup are untouched, so the rebuild still
finalizes correctly and the index remains complete for every row it is responsible for.
The fix does not disable or weaken search to dodge the error — the regression tests assert
that base FTS still returns results afterwards.
`TestFtsRebuildFinishWithoutTrigram` in `tests/test_hermes_state.py`:
- `test_rebuild_finish_skips_trigram_when_unavailable` — drives `_fts_rebuild_finish()`
directly on a trigram-less runtime; asserts it completes, clears both rebuild markers,
and leaves base FTS searchable.
- `test_optimize_fts_storage_succeeds_without_trigram` — end-to-end through the public
`optimize_fts_storage()` entry point; asserts `ok=True`, markers cleared, search intact.
Both use the existing `_NoTrigramConnection` helper already in the file. Both fail on
`main` with `no such table: messages_fts_trigram` and pass with this patch.
`tests/test_hermes_state.py` passes in full (463 tests → 465 with these two). `ruff` clean.
This PR is the crash only.
A companion PR narrows `_db_opens_cleanly()` so that
`hermes sessions repair --check-only` stops reporting a write-broken FTS schema as
healthy — the gap that makes this class of problem hard to diagnose in the first place.
The two are independent and can land in either order.
…44-fold fix(desktop): stop the inflight dump sandwiching structured mid-turn rows
…_summary Simplify-pass finding: _listing_group_label already falls back to 'other' for empty source names, and _classify_source guarantees source_name=='' only when source=='other' — both legs were dead by construction. Aligns the summary path's grouping with the listing path.
Cross-PR interaction fix: NousResearch#77714 (salvage of NousResearch#71775) changed _available_entries to return (available, pending_refresh) while NousResearch#77631 (salvage of NousResearch#67642) added next_available_at() which still truthiness- tests the bare return. A non-empty tuple is always truthy — even ([], []) — so the reset-aware gate silently returned None ('no wait info') for every exhausted pool, disabling the feature NousResearch#77631 shipped. Unpack the tuple and test the available list. Also adapts the lock-probe test for the RLock introduced by NousResearch#77714 (same-thread non-blocking acquire always succeeds on an RLock; probe from a helper thread instead).
…-stremtec chore: contributor email mapping for stremtec
NousResearch#77774) Needed for the NousResearch#37117 salvage (NousResearch#77696 CI failure).
…-bkstock chore: contributor email mapping for BKStock
…agmas Addresses review from @teknium1 on PR NousResearch#71755: - Extended apply_database_pragmas() to handle cache_size, mmap_size, and temp_store from config.yaml (alongside existing wal_autocheckpoint and journal_size_limit). No hardcoded defaults — all values are opt-in via config.yaml, avoiding policy conflicts with other PRs. - Applied to ALL connection types: writer (_connect_and_init), read_only cross-profile attach, and WAL per-thread readers (_get_read_conn). Previously PRAGMAs only ran on the writer path. - Removed inline PRAGMAs from _connect_and_init — single source of truth in apply_database_pragmas(). - Documented config keys with examples in function docstring.
- tests/agent/test_compression_concurrent_fork.py was merged empty; restored the full upstream file (incl. _build_agent_with_db used by test_idle_compaction_lock_and_guards). - agent/context_compressor.py: re-add is_compaction_summary_message (upstream API consumed by plugins/memory/test_holographic_auto_extract). - hermes_cli/web_server.py + web_routers/sessions.py: re-add the CN _normalize_message_content metadata-stripping filter (fork feature; upstream's router has no equivalent) and wire it into get_session_messages.
…arg fix The merge replaced 'import json' with 'import orjson' across the tree (fork's orjson convention), but upstream's code paths still call json.* with stdlib kwargs (ensure_ascii/sort_keys/default=str), producing NameError/TypeError at runtime. Add back 'import json' where json.* is used, and convert gateway/run.py's _j.dumps(sort_keys=True) to option=_j.OPT_SORT_KEYS (orjson API).
Phase-2 test fixes by area (see records/testfix-fixA..E.md): - agent/run_agent: models_dev background-refresh restored (P-028), model_metadata duplicate fn + estimator parity, tool_executor split/dup-callback fixes, sanitizer heal removed (P-024 drop contract), steer _pending_steer mirror (P-023/041), stale-stream bounded escalation restored (P-022). - gateway: relay/media import json placement, _agent_config_signature orjson bytes fix, win32 skips (systemd/update bash/PYTHONUNBUFFERED/runtime_footer POSIX paths). - hermes_cli: config _sanitize_env_lines upstream no-split contract + _SECRET routing, config_defaults CN model_catalog mirror URL, model_catalog _fetch_manifest_with_fallback restored, hermes_state retag separator, terminal_tool json.decode fix, win32 skips (cmd_update npm repair, restart_loop bash/mkfifo, profiles 0600, service_manager mkfifo, web_ui_build fcntl, update_stale_dashboard systemd, tui_npm_install paths). - tools: cli _stop_continuous rename, tts_tool import platform, clarify_tool multi-select, process_registry PTY pid + safe_command, file_operations search zero-match/multiline/multipath + BOM probe + in-proc verify, terminal_tool strip_ansi + pwsh desc, transcription list-mode, win32 skips (POSIX-only tests). - misc: cua_backend WSL posix path fix, photon adapter import json, mcp_serve utime fix, tui_gateway swarm toolset + persist_user_message mock, win32 skips (bang_shell, install_sh, iron_proxy perms, packaging guard, bitwarden perms).
…tract - tools/file_operations.py: _prim_read_sample decodes via the P-037 UTF-8 → ANSI-code-page → lossy chain instead of errors='replace' (GBK/cp936 text was mangled to U+FFFD and misdetected as binary). - tests/run_agent/test_partial_stream_finish_reason.py: empty partial-stream stub is DROPPED by the fork's fused sanitizer (P-024), not healed into '[response interrupted]' (upstream's heal not adopted).
- agent/model_metadata.py: _wire_message_shadow fast path now excludes reasoning_details (preflight estimate must ignore reasoning payloads). - agent/coding_context.py + agent/system_prompt.py: render workspace/home paths with forward slashes (cross-platform prompt stability). - agent/prompt_builder.py: removed duplicated in-loop skills_by_category categorization (M2 org/collision pass owns it) — fixes UnboundLocalError 'category' and the org-skill name-collision flag count. - agent/conversation_compression.py: removed the fork's early 'already continued' skip-guard — upstream's adopt-live-compression-child path (restored in this sync) must win so a stale contender continues on the winner's child transcript. - tests/agent/test_compression_review_76354.py: db.close() before tmpdir cleanup (Windows can't unlink an open SQLite file).
…import-json merge The phase-1 'import json' insertions placed imports before 'from __future__ import annotations' in ~1300 files (SyntaxError at import) and, in a first repair pass, before module docstrings (silently dropping __doc__). This pass repositions every 'from __future__' statement to its legal slot: after the module docstring when one opens the file, otherwise at the top. Module docstrings restored for agent/turn_summary.py, hermes_cli/memory_setup.py, hermes_cli/oneshot.py. Also fixes tests/plugins/memory/test_openviking_provider.py (json.dumps returns str — drop .decode; orjson.dumps returns bytes — keep .decode) and hermes_cli/status.py local-import indentation. Verified: full-tree py_compile clean (0 failures), module __doc__ sample intact.
…gate) - tests/cli/test_cli_save_config_value.py: patch the live get_hermes_home() resolver (P-027 contract) instead of the import-time constant. - gateway/relay/__init__.py: restore local 'import json' (self-provision). - gateway/run.py: /agents command now renders background async delegations (stalling/no-progress) per upstream's slash_commands.py section. - plugins/platforms/google_chat/adapter.py: write_text encoding (utf8 gate). - tests: Windows-adapt media URI/path assertions (unquote + normpath + USERPROFILE for tilde) and skip the POSIX /etc filesystem test on win32.
…viously-failing files green)
… + POSIX utilities) Sync tools/environments/bash_fix.py with the new kimi-agent implementation (python-only; no native acceleration in this project): - New cmd-style fallbacks: copy, move, del, erase, ren, rename, rd, md, chdir, cls, xcopy, mklink, findstr, fc, where, tasklist, taskkill, systeminfo - New POSIX utility fallbacks: watch, killall, pidof, column (perl) - netcat alias sharing the nc /dev/tcp fallback - New PowerShell snippets: _TASKLIST_PS, _TASKKILL_PS, _SYSTEMINFO_PS, _KILLALL_PS, _PIDOF_PS, _COLUMN_PERL (__HERMES_* env vars) - Keep the project's correctness-first 'from agent.re_compat import re' instead of kimi's direct regex import Tests: extend tests/tools/test_bash_fix.py — move column/watch out of the 'not rewritten'/'preserved' lists into the fallback replacement cases and cover all new fallbacks; 211 passed. pwsh_fix.py unchanged (kimi delta is native-acceleration only).
… powershell 5.1 When terminal.shell is unset (auto), resolve git-bash first (if a working install exists via _find_bash + _bash_starts smoke test), then PowerShell 7, then Windows PowerShell 5.1. Explicit bash/pwsh/powershell are respected unchanged (explicit PowerShell never probes git-bash). - local.py: _find_bash gains raise_if_missing=False (None instead of raising for the auto path, keeping the helpful RuntimeError for explicit callers); _resolve_shell auto branch reordered git-bash-first; new _build_bash_background_script mirrors the PowerShell wrapper for bash (cd guard + eval + pwd->cwd_file + $? exit); docstrings updated. - terminal_tool.py: _detect_shell_for_description mirrors _resolve_shell (explicit bash -> "bash", auto -> bash->pwsh->powershell, unknown -> auto); Windows bash platform sentence added to the dynamic description. - process_registry.py: Windows background/PTY spawns follow the resolved shell — bash uses [shell, -lc, script], PowerShell keeps -NoProfile. - prompt_builder.py: auto shell hint prefers the git-bash hint when present. - Docs (EN + zh-Hans windows-native.md, environment-variables.md, READMEs, quickstart, skill docs) + config.py comment updated for the new default. - Tests: updated the 5 affected files + new tests/tools/test_find_bash_optional_probe.py; ~6700 tests in the touched areas pass; ruff check . clean. (2 pre-existing MSYS pathconv failures reproduce on untouched origin/main.)
…ell preference + CN fork patches) into dev-020
…nc with merged pyproject)
Adapt the fork line's tests/handlers to dev-020's refactored modules while keeping the fork's behavior improvements: - process_registry: pin _resolve_shell in the Popen-leak test (spawn_local now resolves the shell first per P-058). - terminal_dynamic_description: P-058 test uses dev-020's '(use read_file)' phrasing. - terminal_task_cwd: rebuild as fork's 13 tests + dev-020's test_explicit_workdir_does_not_persist (orjson + terminal_output_stream imports restored). - system_prompt: profile hint + Managed Desktop block go to post_workspace_parts (dev-020's cache layout); test expects P-057 text with native separators. - web_server: fix activate-gate indentation (handler returned None/200); add activate field already in web_models; keep #122's memory_char_limit field. Adapt fork tests: 422 pagination validation (dev-020 Query(ge=0)), drop replace_messages on compression-closed session, mock transcribe_audio(**kwargs) + import orjson. - tui_gateway: port P-056 non-string text rejection (-32602) into methods_prompt, add magic-byte validation to image.attach_bytes (4016), port fork's cron.manage success=False/ValueError->4023 classification. - release-runtime: verify loop covers bundled web providers (ddgs, exa_py, firecrawl_py, parallel_web) matching the merged pyproject cn-desktop extra. - web plugin imports: keep dev-020 absolute package-path style; update the fork's relative-import test to guard it. All related tests pass except the pre-existing MSYS pathconv failure (test_init_session_bootstrap_rewrites_backslash_snapshot_paths), which also fails on untouched dev-020.
P-058 made git-bash the Windows auto shell default; dev-020's PowerShell-feature and shell-agnostic tests assumed the old powershell default: - terminal_tool: drop the fork's unconditional result['cwd'] key; keep dev-020's conditional cwd-echo contract (fixes test_terminal_cwd_echo and test_terminal_truncation_spill). - Pin HERMES_SHELL_TYPE=pwsh via autouse fixtures in the PowerShell-only test files (test_local_pwsh_session, test_local_pwsh_warnings, test_terminal_truncation_spill [uses the PS '&' operator], test_terminal_cwd_echo) and per-test in test_windows_perf_optimizations (session-reuse + cmd-fast-path live tests).
With git-bash now the resolved Windows shell (P-058), the P-052 MSYSTEM neutralization activates and the command lands in the wrapper as 'export MSYSTEM=; echo ok' inside the eval. Assert the bash_fix contract (command un-fixed inside the eval region) instead of the exact spelling.
Eynzof
marked this pull request as ready for review
August 7, 2026 01:34
Eynzof
force-pushed
the
dev-020
branch
3 times, most recently
from
August 7, 2026 02:56
c804c59 to
963fcc5
Compare
Merged
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.
What changed
dev-020back intomain.dev-020is ahead ofmainby 4,222 commits.Notes
origin/mainis an ancestor oforigin/dev-020, so this branch has not been merged intomainyet.dev-020before creating this one.Validation
git fetch origin main dev-020.origin/dev-020is not an ancestor oforigin/main.origin/mainis an ancestor oforigin/dev-020.