Skip to content

chore: 合并 dev-020 到 main - #143

Merged
Eynzof merged 4225 commits into
mainfrom
dev-020
Aug 7, 2026
Merged

Eynzof merged 4225 commits into
mainfrom
dev-020

Conversation

@Eynzof

@Eynzof Eynzof commented Aug 7, 2026

Copy link
Copy Markdown
Owner

What changed

  • Merge dev-020 back into main.
  • dev-020 is ahead of main by 4,222 commits.
  • Diff size at PR creation: 5,690 files changed, 578,690 insertions, 417,956 deletions.

Notes

  • origin/main is an ancestor of origin/dev-020, so this branch has not been merged into main yet.
  • No existing PR was found for head branch dev-020 before creating this one.
  • Opened as a draft because the branch is large and should go through CI/review before being marked ready.

Validation

  • Checked remote refs with git fetch origin main dev-020.
  • Verified origin/dev-020 is not an ancestor of origin/main.
  • Verified origin/main is an ancestor of origin/dev-020.

kshitijk4poor and others added 30 commits August 3, 2026 18:44
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
…-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.
… + 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
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
Eynzof marked this pull request as ready for review August 7, 2026 01:34
@Eynzof Eynzof added the ci-reviewed applied to manually approve dangerous changes label Aug 7, 2026
@Eynzof
Eynzof force-pushed the dev-020 branch 3 times, most recently from c804c59 to 963fcc5 Compare August 7, 2026 02:56
@Eynzof
Eynzof merged commit 1119e4b into main Aug 7, 2026
49 checks passed
@MaxwellGengYF
MaxwellGengYF deleted the dev-020 branch August 13, 2026 04:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-reviewed applied to manually approve dangerous changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.