Commit Graph

529 Commits

Author SHA1 Message Date
Teknium d63e380324 compat(plugins): warn once per name when a plugin resolves an old import path; lint step restored in CI
Every PLUGIN-COMPAT __getattr__ now calls hermes_cli.plugin_compat.warn_once(facade, name, target) before
resolving, emitting a HermesPluginCompatWarning (FutureWarning) once per process per name: old path, new
path, removal target. Importing a facade for its live API stays silent; only resolving a moved name warns.
COMPAT_MANIFEST.md documents the warning and how to silence it during migration.

Verified the runtime never routes through a pointer: every entry point (run_agent, cli, hermes_cli.main,
gateway.run, tui_gateway.server, web_server, model_tools + tool discovery, hermes_state, cron.scheduler,
browser_tool, mcp_tool, kanban, auth) imports clean and `hermes doctor` runs end to end with the warning
promoted to an error.

Also restores the check_compat_pointers CI step to .github/workflows/lint.yml, which a0be177aac dropped
when the compat layer was regenerated (the lint script itself was present; the workflow step was not).

hermes_cli/plugin_compat.py, tests/test_plugin_compat_warning.py and the two-line insert per facade are
part of the compat layer and go away with it.
2026-09-04 00:15:16 -07:00
Teknium 2776813df3 compat(plugins): temporary import-path shims for external plugins — ONE commit, revert on schedule
The Sep 2026 decomposition (PR #102117) makes internal import paths a non-API: names now live in
the focused modules that define them. This commit is the ONLY thing keeping the old paths alive,
so external plugins have time to update. It is deliberately a single, unsquashed commit:

    git revert <this sha>

removes every shim, stub and manifest at once on the announced date. Nothing in-tree may depend on
these pointers: scripts/check_compat_pointers.py (wired into lint.yml) fails CI if it does.

What it adds (see COMPAT_MANIFEST.md, compat_manifest.json):
- 332 facade modules get one delimited `PLUGIN-COMPAT` block appended at the end of the file
- 1,172 moved names resolved lazily via a module `__getattr__` (PEP 562) — never a top-level import,
  so no import cycles; facades that already had `__getattr__` get a chained one
- 592 third-party/stdlib names the old modules used to expose, with their original import statements
- 266 public definitions that had been deleted as unused, restored byte-for-byte from the pre-decomposition
  tree (+40 private helpers and 16 imports pulled in only because a restored definition needs them)
- 3 deleted modules recreated as re-export stubs (gateway/startup_watchdog, hermes_cli/observability/
  relay_runtime, tools/environments/modal_utils)
- private names (`_x`) get no pointer: they were never API (3,792 skipped)

Verified: all 335 touched modules import under a fresh HERMES_HOME and every manifest name resolves;
the lint reports zero in-tree uses; ruff clean; targeted suites unchanged.
2026-09-03 17:13:22 -07:00
Teknium 53db597201 simplify(compat): hermes_state — drop 81 re-exports + 3 registry aliases + 3 shims, repoint 45 callers + 60 test files
hermes_state.py: delete every '# noqa: F401 (re-exported...)' import block (hermes_state_common/errors/guard/
readpool/sessions/fts/dbfile/wal/repair/registry + agent.context_compressor _DB_PERSISTED_MARKER_KEY); keep
only the names hermes_state.py itself uses, without noqa.
hermes_state_registry.py: drop get_shared_session_db/release_shared_session_db/close_shared_session_dbs
aliases; every caller (gateway/, tools/, tui_gateway/, cron/, mcp_serve, run_agent, tests) now imports
acquire/release/close_all/release_or_close from hermes_state_registry.
hermes_state_titles.py: drop set_auto_title_if_empty shim (title_generator keeps its getattr fallback).
Re-remove shim-only names restored by 34abf954bd: latest_user_message_row_id (tests call
latest_message_row_id(key, role='user'); role-targeting assertions kept) and get_session_activity (tests
build the snapshot via agent.session_activity.build_activity_snapshot over db.get_session(sid)).
hermes_state_wal._log_once resolves its dedupe sets as module globals instead of via hermes_state;
hermes_state_repair helpers call module globals directly (tests patch hermes_state_repair.<name>).
Frozen updater surface untouched (update_cmd_maint imports only SessionDB from hermes_state).
2026-09-03 13:46:50 -07:00
Teknium e83816a4d1 review-fix(comments): restore lost #NNNN rationale comments across non-test source (mechanical sweep, condensed, code unchanged)
For each issue anchor present in BASE 63279301bc non-test .py and absent on HEAD, the BASE comment/docstring block was re-attached at the HEAD location of the code it explained (matched by the distinctive code line / enclosing def). Sentences already covered by an existing HEAD comment were deduped; the issue number always survives. Insert-only: no code lines changed.
2026-09-03 09:44:26 -07:00
Teknium 5a0efda992 review-fix(comments): restore reviewer-named rationale blocks (#4926, #81335, #54220/#56747, #100436, #5057/#6252/#10370/#4665) 2026-09-03 09:32:25 -07:00
Teknium 0071ba9965 Merge origin/main (561b053f79) into simp/forwardport: forward-port 220 main commits into the simplified tree 2026-09-03 03:31:03 -07:00
Teknium 7435b0c779 refactor(state): hoist the shared no-more-rows retry out of _execute_write's three except arms 2026-09-03 00:07:51 -07:00
Teknium d944616a6d refactor(state): compact narrative docstrings and comments in hermes_state and hermes_state_sessions (keep every WHY) 2026-09-03 00:04:03 -07:00
Teknium cfb268a6c9 refactor(state): table-drive the session upsert keep-existing tail, compact module-constant comments 2026-09-02 23:55:33 -07:00
Teknium 3056f46611 refactor(state): drop dead get_session_activity, merge generation guards and holder scan branches, pack tuning constants 2026-09-02 23:48:44 -07:00
Teknium 338a6309ef refactor(state): unify read-only connect, table-drive session filter WHERE, compact tuning comments 2026-09-02 23:32:25 -07:00
Teknium 54714ef066 refactor(state): table-drive parent-metadata inheritance SQL, unify end-stamp bump and delete cascades, collapse defensive layers in hermes_state_sessions 2026-09-02 23:17:22 -07:00
Andrew Wikel 5a7bee0fa8 fix(state): exclude tool calls from trigram FTS
Keep structured tool_calls searchable through the standard FTS index while
removing their repetitive JSON from the trigram projection. Reuse the
existing optimize-storage rebuild path for deployed v1 layouts.

Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
2026-09-03 11:35:13 +05:30
kshitijk4poor 935c1e8962 test(state): prove the repair never reopens state.db after the guard releases
Replace the requires_wal-gated unit assertion (which never runs on WAL-reset-
vulnerable runtimes such as macOS 3.46) with an end-to-end test through
repair_state_db_schema that traces every _connect_repair_durable call against
the guard's enter/exit and fails on main's shape (a connect after guard-exit).
Runs on every platform. Docstring now names the actual hazard.
2026-09-03 11:30:36 +05:30
joaomarcos 86c4a23d46 fix(state): avoid WAL unlink race during repair
(cherry picked from commit 66d54c619d7eaa0f7b4db72c994699778a20d252)
2026-09-03 11:30:36 +05:30
Teknium c8616a9127 refactor(state): drop unreferenced re-exports and dead helpers in hermes_state; fold open path into _open_writer, unify read-conn close logging 2026-09-02 22:59:30 -07:00
sal a15f96450b fix(recovery): make the printed salvage command satisfy the real CLI contract
Review blocker on e62940d: every state-db guidance site printed

  hermes sessions recover --source <db>

but cmd_sessions rejects that shape with exit 2 ("--output is required
unless --inspect-only is used") before any snapshot is taken — the user
follows the instruction during a corruption incident and gets nothing.

All five state-db sites now print the established two-stage operator
contract (the same shape `sessions repair` failure output and
docs/state-db-recovery.md already use):

  hermes sessions recover --source <db> --inspect-only
  hermes sessions recover --source <db> --output recovered-state.db

with the stop-the-gateway precondition stated for the gateway/turn
banners, and --inspect-only leading in the hermes_state refusal strings
(inspection before writing anything).

New TestEmittedCommandsSatisfyCliContract dispatches the exact emitted
flag shapes through the real cmd_sessions and asserts they pass the
contract gate (rc != 2) on a scratch DB, plus a premise test pinning
that the v1 no-flag shape is still rejected with rc 2 — so a guidance
string can never again pass a source-substring test while the command
it prints deterministically fails.

Noted for merge order: #101423 and #101168 also touch
hermes_cli/session_recovery.py. They are complementary recovery-integrity
work, not duplicates of this guidance/gate fix; whichever lands second
should rebase and rerun the lost_and_found + session-recovery suites.

(cherry picked from commit 34dc59a284509e76a0342c36d03a2a437aa8a3b9)
2026-09-03 11:28:21 +05:30
sal 5d9a2110ba fix(recovery): stop pointing sqlite3 .recover guidance at the live state.db
Refs #100368. The forensics thread established that a sqlite3 CLI with
the WAL-reset opener bug (fixed 3.51.3+ / backports 3.50.7 / 3.44.6;
Debian/Ubuntu system shells 3.45.1/3.46.1 are in the vulnerable band)
unlinks the live -wal/-shm pair when pointed at a live state.db whose
writer's DMS lock has been cancelled, splitting the store into two
concurrent generations whose acknowledged writes vanish while both
report integrity_check ok. Hermes' own corruption banners instructed
exactly that command.

- gateway corruption broadcast, run_agent corrupt-cause explanation,
  hermes_state repair-budget and forensic-backup refusals, and the
  kanban manual-recovery hint now route operators to
  `hermes sessions recover --source <db>` (which snapshots the damaged
  bundle before any shell touches it) and warn against a raw sqlite3
  shell on the live file
- find_sqlite3_cli() now refuses a WAL-reset-vulnerable shell for the
  page-level salvage lane even on the snapshot, reusing the canonical
  gate from hermes_cli.sqlite_runtime so the embedded runtime and the
  salvage shell can never disagree
- find_sqlite3_cli_refusal() records why a shell was refused so the
  lost_and_found lane can tell the operator exactly what to install
  instead of a generic "not found"
- regression tests cover the version gate (vulnerable/fixed matrix, the
  mirror check), every refusal reason, and each guidance site

Test plan:
- scripts/run_tests.sh tests/hermes_cli/test_sqlite3_cli_salvage_gate.py
  tests/test_state_db_repair_loop_cap.py
  tests/run_agent/test_corruption_recovery_guidance.py
  tests/hermes_cli/test_session_recovery_lost_and_found.py
  tests/hermes_cli/test_session_recovery.py tests/test_sqlite_wal_reset_gate.py
  tests/hermes_cli/test_sqlite_runtime.py - 91 passed, 1 skipped locally

(cherry picked from commit e62940d1021e80e9b7d6423ced1cbdfe7dd0c37d)
2026-09-03 11:28:21 +05:30
Teknium 833ffda3d6 refactor(state): repack hermes_state re-export import blocks 2026-09-02 20:32:35 -07:00
Teknium 71242c419f refactor(state): fold replaced/WAL-generation guards into two halt helpers 2026-09-02 20:25:40 -07:00
Teknium 3c9a51bbf1 refactor(state): unify writer-conn setup, drop dead attrs/branches, compact hermes_state comments 2026-09-02 19:53:29 -07:00
Teknium d254525a39 refactor(state): extract session lifecycle/listing to hermes_state_sessions and FTS setup to hermes_state_fts 2026-09-02 19:20:36 -07:00
Teknium 182dc4b313 refactor(state): extract read-connection budgeting to hermes_state_readpool 2026-09-02 18:50:30 -07:00
Teknium e527b0699d refactor(state): extract error types/classifiers to hermes_state_errors and the live-DB guard to hermes_state_guard 2026-09-02 18:44:39 -07:00
Teknium 30b4685036 refactor(state): drop 52 unused back-compat re-exports from hermes_state 2026-09-02 18:28:11 -07:00
Teknium eb8d628c97 refactor(hermes_state): restore WHY comments dropped by round-2 sub-branches
Comment/docstring-only (AST-identical): surrogate-scrub rationale, persisted
marker stripping invariant, generation counter upgrade semantics, CJK marker
empty-vs-populated rule, WAL 0-page ordering precondition, repair backup
live-connection case, telegram topic delete precondition, mixed-mode
corruption definition, and similar.
2026-09-02 16:48:30 -07:00
Teknium b32dbc98f5 refactor(state): final docstring/comment tightening (IDENTICAL-AST) 2026-09-02 16:27:31 -07:00
Teknium 2ae3e4d6bd refactor(state): share /proc fd walker, collapse loops in generation/holder probes 2026-09-02 16:22:10 -07:00
Teknium f4e6ccc5f5 refactor(state): unify db-generation halt guard, hoist _is_no_more_rows, collapse flag inits 2026-09-02 16:17:27 -07:00
Teknium 40fe17f83b refactor(state): fold adjacent string fragments that fit one line (AST-identical) 2026-09-02 16:14:54 -07:00
Teknium 0fe9d5f5ae refactor(state): AST-neutral formatting compaction (blank lines, signatures, arg lists) 2026-09-02 16:13:30 -07:00
Teknium c023f00c9c refactor(state): compact session-CRUD comments/docstrings (keep every WHY) 2026-09-02 16:09:13 -07:00
Teknium e4947b6871 refactor(state): compact SessionDB core comments/docstrings (keep every WHY) 2026-09-02 16:00:21 -07:00
Teknium 0679a0ca15 refactor(state): compact module-level comments/docstrings (keep every WHY) 2026-09-02 15:54:56 -07:00
Jason Wu 98a6e4a90e fix(session-search): bound recent-session browsing
Preselect indexed recent candidates before rich hydration, cap and deduplicate compression lineage traversal, and interrupt sustained SQLite work through a cooperative progress deadline. Fail closed when the bounded browse API is unavailable and cover legacy-schema reconciliation plus malformed lineage cases.
2026-09-03 04:23:35 +05:30
Teknium 2242f55e9f refactor(state): unify session CRUD helpers, list filter builder, compact re-exports 2026-09-02 15:49:46 -07:00
Teknium 62e8c8ffbc refactor(state): lift SessionDB.__init__ nested closures into methods 2026-09-02 15:29:40 -07:00
kshitijk4poor 5e01b8fa7a refactor(state): one canonical-path helper for holder scans
Follow-up on the #97330 salvage (#97329): main grew `_canonical_sqlite_path`
after the PR's base; the holder module re-implemented it as a closure. Export
`canonical_sqlite_path` from hermes_state_holders and alias the legacy name in
hermes_state. Drop the unused `_read_proc_argv` / `_looks_like_hermes`
re-exports (no in-tree callers).
2026-09-03 03:50:43 +05:30
Benjamin PERRY 06d7b77b1c fix(state): block repair on deleted SQLite holders
Move live-holder inspection into a bounded helper and make repair fail closed when a process still owns the state database, including deleted WAL/SHM descriptors and ambiguous procfs reads.

Preserve the contributor lineage from the original four-commit review train while presenting one coherent release object on current main.

Co-authored-by: fangliquanflq <fangliquan@qq.com>
Co-authored-by: ciabata <296402666+ciabata-git@users.noreply.github.com>
2026-09-03 03:50:43 +05:30
Teknium d15c61b5dc refactor(state): split SessionDB into domain mixins and free-function modules; unify SQL boilerplate
hermes_state.py 17,220 -> 6,442 LOC. Behavior-neutral: every moved body is
AST-identical to the original, verified per extraction.

SessionDB core
- _write_sql / _write_rowcount / _read_one / _read_all replace ~120 copies of
  the `def _do(conn): conn.execute(...)` + `_execute_write(_do)` and
  `with self._read_ctx() as conn: row = conn.execute(...).fetchone()` shapes.
- _set_lineage_column replaces four copies of the recursive compression-lineage
  UPDATE (archived / pinned / hidden / last_read_at).
- _read_session_number unifies the three compression counter readers.
- Dead (zero refs repo-wide): restore_rewound, delete_gateway_routing_entries,
  _is_duplicate_replayed_user_message, SessionPortabilityMixin.get_first_assistant_text.

New mixins bound onto SessionDB via the MRO (logger name stays "hermes_state"):
  hermes_state_messages    SessionMessagesMixin       48 methods
  hermes_state_compression SessionCompressionMixin    30
  hermes_state_gateway     SessionGatewayMixin        26
  hermes_state_maintenance SessionMaintenanceMixin    13
  hermes_state_usage       SessionUsageMixin          12
  hermes_state_titles      SessionTitlesMixin         13
  hermes_state_telegram    SessionTelegramTopicsMixin 11
Origin-internal symbols resolve through a lazy `from hermes_state import ...`
inside the few methods that need them (no import cycle).

New free-function modules, every name re-imported into hermes_state so
`hermes_state.<name>` (and test monkeypatches on it) keep working; intra-module
calls to patched helpers go through the lazy origin import:
  hermes_state_repair   repair/backup/preflight (43 defs)
  hermes_state_wal      journal-mode / PRAGMA policy (33 defs)
  hermes_state_dbfile   header probes, zeroed-db quarantine, stats, holders (21 defs)

Existing mixins: search — shared FTS MATCH/LIKE builders, unified rebuild
status/step/finish engines, state_meta helpers; schema — one legacy/v23 FTS init
branch, shared _live_pk_columns, Row/tuple dual access dropped; portability —
shared _PREVIEW_RAW_SUBQUERY_SQL and _rich_row; common — single
stat_db_file_identity (was 3 copies), AUTO_VACUUM_MIN_FREELIST_RATIO.

Docstrings/comments hand-compacted (AST-identical) keeping every invariant,
ordering rule, failure mode and WHY. Schema SQL, migration order and PRAGMAs
untouched. test_repair_path_has_no_bare_connects repointed to hermes_state_repair.
2026-09-02 13:32:13 -07:00
Brooklyn Nicholson ab281990b8 fix(sessions): include compaction-archived rows in every display projection
The gateway's three display reads — session.resume, the ancestor lineage
prefix, and the warm-session payload — filtered active = 1, so a compacted
conversation rendered as its summary plus the carried-forward tail. The REST
transcript read has included the archived rows since #80680, so the same
session read two ways gave two different answers.

Extract the generation-dedupe policy the REST read carried inline into a
shared _dedupe_display_generations() and point every display projection at
it. The model-fed history stays active-only: compaction still compresses the
working context, it just no longer erases the user's transcript.
2026-09-02 12:11:15 -05:00
Teknium 73f68362b3 fix(sessions): auto-prune state.db by default (90d) and gate VACUUM on freelist ratio (#54189)
Flip the state.db retention defaults per Teknium's decision on #54189:

- sessions.auto_prune: false -> true. A stock install now prunes ENDED
  sessions inactive for retention_days at CLI/gateway/cron startup
  (at most once per min_interval_hours). Open, pinned and mid-turn
  sessions are never deleted; the only open rows touched are stale
  automation sessions (#100903 sweep), which are closed, not deleted,
  and aged a further full window before removal.
- sessions.retention_days stays 90 (already the default; verified).
- Auto-VACUUM is now additionally gated on the reclaimable fraction of
  the file: PRAGMA freelist_count / page_count must exceed 25%
  (AUTO_VACUUM_MIN_FREELIST_RATIO) on top of the existing
  min_vacuum_interval_days throttle. Pruning a few small sessions on a
  dense multi-GB DB no longer rewrites the whole file to reclaim a few MB.
  Unknown ratio (pragma read failure) falls back to the time throttle.

Existing installs that explicitly set any sessions.* key keep their
values (load_config deep-merges DEFAULT_CONFIG under user YAML); only
unset keys pick up the new defaults. No _config_version bump needed.
cli-config.yaml.example documents the section commented-out so
installers that copy it verbatim never pin these as explicit settings.

Tests: ratio gate (below/above/at-threshold/unknown/override), real-DB
freelist ratio, default assertions, fresh-config startup hook reaches
the prune call, explicit opt-out respected, template-does-not-pin-keys.
2026-09-02 07:26:52 -07:00
Teknium 8e4366d358 fix(tools): freeze tools[] across agent-cache eviction; make /reload-mcp the re-probe hatch
Policy: availability-gated tools (check_fn probes — Docker, HASS_TOKEN,
OAuth…) are frozen for the life of a session. tools[] only changes on
/new, /reload-mcp, or compaction. Two doors remained after #100638:

* Gateway agent-cache eviction (LRU/idle sweep/cross-process invalidation)
  rebuilds a fresh AIAgent for the SAME session and agent_init re-derives
  agent.tools from live probes with no predecessor to preserve. Persist
  the session's resolved tool-name order in a new `sessions.tool_names`
  JSON column (declarative reconciliation, SCHEMA_VERSION 28), written
  alongside the system prompt and re-pinned on every published refresh
  (so /reload-mcp and compaction naturally reset it; /new mints a new
  row). On restore-for-existing-session the fresh definitions are folded
  onto the saved order via the SAME `_merge_preserving_prefix` helper —
  a probe-flipped tool is carried forward from the registry schema, a
  deregistered one dropped, new tools appended at the tail.

* /reload-mcp (CLI, gateway, TUI RPC) now also calls
  `reprobe_tool_availability()` — drops the check_fn verdict cache and the
  get_tool_definitions memo — so a user can consciously pick up a
  credential/daemon that appeared mid-session. Docs updated.
2026-09-02 07:22:59 -07:00
Teknium 7463cd1202 fix(session): fence multiplex peer-fallback recovery and profile inheritance by owner (#74285, #88381)
The per-profile store partition (17ba992108, 5ffaed6e45, 5cc3da6827)
already keeps fresh rows apart, but legacy rows written to root state.db
before the partition still sat where the default profile's peer-tuple
fallback could adopt them: a Telegram DM's tuple (chat_id == user_id, no
thread) is identical for every bot. Three residual holes, closed with the
smallest predicate that fits main's design:

- hermes_state.find_latest_gateway_session_for_peer: the fallback query
  now requires COALESCE(s.profile_name, <store owner>) = <store owner>
  (owner via SessionDB._own_profile_name). Handles NULL legacy rows and
  the single→multiplex migration case a key-namespace fence would break;
  stores outside the profile tree (no derivable owner) are unchanged.
- gateway/session._recovered_row_allowed_for_active_profile: under
  multiplexing no longer `return True` — the recovered row's agent:<ns>:
  must match the REQUESTED key's namespace (the active profile is
  meaningless when several profiles serve concurrently). Single-profile
  behavior unchanged; keyless/unnamespaced rows stay adoptable.
- hermes_state create_session parent COALESCE: profile_name inherits only
  when parent and child agree on agent:<ns>: (or either is keyless), so a
  default child forked from a sibling row is not durably mislabelled.

Co-authored-by: pcaruba <31041167+pcaruba@users.noreply.github.com>
Co-authored-by: jiangtaoliu-source <308256854+jiangtaoliu-source@users.noreply.github.com>
Co-authored-by: 69k4xmdfm2-blip <275826864+69k4xmdfm2-blip@users.noreply.github.com>
2026-09-02 06:59:11 -07:00
Teknium 458e2ef1d2 refactor(state): collapse telegram topic v3 migration into one table-driven rebuild
Same behavior as the salvaged #76487 migration (fresh installs get the v3
shape; v1/v2 tables rebuild with profile_name leading the PK, legacy rows
into 'default' only, CASCADE FK supplied on the way), with the per-table
DDL written once instead of three times and the now-redundant v1->v2
CASCADE-only rebuild dropped (the v3 rebuild subsumes it).

Co-authored-by: Celio Monteiro <crdesign8@hotmail.com>
2026-09-02 05:59:24 -07:00
Celio Monteiro 351e4c0067 fix(state): namespace telegram topic tables by profile_name
Issue #76423: under multiplex_profiles a shared state.db keyed topic mode
and bindings only by Telegram chat_id/thread_id, so private-chat ids
collided across bots/profiles.

- Add profile_name to telegram_dm_topic_mode and telegram_dm_topic_bindings
- Schema v2→v3 rebuild; legacy rows migrate into the "default" namespace
- Keyword-only profile_name="default" on SessionDB topic APIs (compat)
2026-09-02 05:59:24 -07:00
kshitijk4poor e9fa7bc05e fix(state): keep the #94736 teardown self-heal alive under the deleted-WAL guard
Follow-ups on the salvaged #101081 guard:

- A clean close() lets SQLite unlink the WAL sidecars legitimately; the
  guard treated that as a lost generation and permanently halted the
  handle, so the #94736 late-write self-heal reopen dropped transcript
  tails (4 existing tests failed). close() now clears the recorded
  sidecar generation, and _wal_generation_was_lost() re-adopts the
  current sidecars after a clean /proc/self probe instead of relying on
  a stale snapshot.
- Healthy writes no longer walk /proc/self/fd: once a sidecar
  generation is recorded, the stat-based inode check alone detects an
  unlink/replace. The fd probe only runs in the empty-identity state
  (fresh DB, post-close reopen).
- DeletedWalGenerationError now subclasses StateDbReplacedError, so the
  gateway retry queue and run_agent flush divert transcripts to the
  JSONL fallback exactly as they do for a replaced store, instead of
  retrying forever against a halted handle.
- __init__ refuses once (under the startup lock) instead of twice per
  open, halving the system-wide /proc scan; dropped the dead
  include_self parameter and the dead _IS_WINDOWS clause.
- Test fixes: rstrip(' (deleted)') char-set bug -> removesuffix; the
  non-linux test now patches sys.platform (the real gate) instead of
  _IS_WINDOWS.
2026-09-02 18:24:54 +05:30
Cursor Agent 7f7df1ce44 fix(state): refuse SessionDB open and writes on a deleted WAL generation
A live writer can keep a deleted state.db-wal inode while a second opener
mints a fresh WAL at the same path. Fail closed on writable open (before
connect) and on the write-path sidecar identity check so the second
generation is never created.

Co-authored-by: Noa <rainbowgore@users.noreply.github.com>
2026-09-02 18:24:54 +05:30
Teknium 89e2e4f572 docs(sessions): explain why the canonical Bot Chat guard is provenance-blind (#99517)
Follow-up to the salvaged #99560 commit: restore the original docstring
wording (user writes still always land everywhere else), name the
llm-outranks-derived hole the guard now closes, and annotate the two new
tests with what each pins.
2026-09-02 05:39:24 -07:00
fangliquanflq fb9a294794 fix(sessions): protect canonical Bot Chat from auto-title 2026-09-02 05:39:24 -07:00