Commit Graph
8648 Commits
Author SHA1 Message Date
Rodrigo Gomes da Silva 7a596f9114 fix(sessions): stop a busy project starving quieter ones in the assigned pass
The per-project budget was applied to the rows of ONE global recency-ordered
query, so it was not a per-project bound: when a single project owns the whole
`limit * len(projects)` window, every other project's assigned conversations are
never considered and their chips show nothing (greptile P1 on #6659, reproduced
at the real 200 as well as scaled down).

Two tiers now:

* the global query still runs first and is the only cost a profile whose assigned
  history fits in the window pays;
* when it comes back SATURATED, one GROUP BY over sessions.project_id says which
  projects still own undelivered rows, and each of those gets its own
  project-scoped query with its own remaining budget. `project_ids` narrows the
  lineage CTE's seed, so a per-project query is cheaper than the global one and
  still returns whole compression lineages.

Cost stays bounded: worst case per build is 1 global query + 1 GROUP BY probe +
PROJECT_ASSIGNED_CLI_REFILL_PROJECTS (32) project-scoped queries, each bounded to
one project's remaining budget. The scan ceiling is now shared equally between
projects (ceiling // len(projects)) rather than first-come, so the recovered
payload stays under the ceiling without letting the loudest project take it all.
2026-09-24 11:03:46 -03:00
Rodrigo Gomes da Silva e19c67adde refactor(sessions): resolve project ownership through _profiles_match
profile_scoped_project_ids() duplicated the body of the canonical
api/profiles.py::_profiles_match, whose docstring says it was relocated there
precisely so callers stop duplicating it. Call it instead.

Behaviour-identical: both spellings agree on all twelve None / '' / 'default' /
renamed-root / foreign-profile combinations, which the new parametrised test
asserts cell for cell against _profiles_match itself. It is a drift guard, not
a regression test — swapping the call back to a raw
`p.get('profile') == active` comparison reddens seven of its twelve cells.
2026-09-24 11:03:46 -03:00
Rodrigo Gomes da Silva b90d64d9cf fix(sessions): never let the unassigned refill drop a resolved chip
_merge_state_row overwrote an already-represented lineage unconditionally, and
recovery pass 2 (the unassigned refill) merges into the same lineage-keyed
table pass 1 fills. A row already carrying a RESOLVED project_id could
therefore be replaced by a bare unassigned projection — deleting the chip this
PR exists to restore and swapping the row's identity from the lineage tip back
to its root.

Reachable wherever the SQL project_assignment filter and the Python lineage
projection disagree about where a compression lineage starts. They differ today
on a blank `source`: _is_continuation_session treats an empty/whitespace source
as "unknown, same conversation", while the lineage CTE compares
LOWER(TRIM(parent.source)) = LOWER(TRIM(child.source)) and so will not walk
from an assigned tip up to a blank-source root. The mixed first pass collapses
the pair into one assigned row; pass 2 then hands the root back as its own
"unassigned" conversation and clobbers it.

The merge is now one-way: overwrite only when the incoming row ADDS a resolved
assignment, or when the row on file has none to lose. Fixing the merge rather
than each possible divergence — it is the one place where "already resolved" is
knowable locally, and pass 2 by construction has less assignment context than
pass 1.
2026-09-24 11:03:46 -03:00
Rodrigo Gomes da Silva 283bf6a047 fix(sessions): keep the interactive exclusion spelled as a literal
tests/test_issue2841_show_cron_sessions_toggle.py reads this call site as
SOURCE TEXT (`'("cron", "webhook", "kanban") if source_filter is None'`), so
routing it through the new BACKGROUND_CLI_SOURCES constant turned a
pre-existing test in the maintainer's own shard gate red for a
behaviour-neutral refactor.

Put the literal back at that one call site. BACKGROUND_CLI_SOURCES stays where
the kanban-chip fix actually needs it (the background-source check in
_interactive_row_project_id, and interactive_excluded for both recovery
passes). A new test builds the expected literal FROM the constant and asserts
it appears verbatim in api/models.py, so the two spellings cannot drift
without failing here rather than in an unrelated file.
2026-09-24 11:03:46 -03:00
Rodrigo Gomes da Silva 05816be3c2 fix(sessions): refill the unassigned window when the mixed pass under-delivers
Recovery pass 2 was gated on `first_pass_count >= unassigned_target`, reading a
short first pass as "the database has nothing left". It can also mean the mixed
pass ran out of RAW candidate window: compression segments are spent from it
before the logical slice, so a lineage-heavy profile gets fewer conversations
than it asked for and the unassigned quota stays short. Also run the refill when
assigned conversations inside the window displaced unassigned candidates, which
is the case a narrower unassigned-only query can actually repair. A profile with
no assignments and a small state.db still issues no extra query.

While here, spend the per-project assigned budget through `_merge_state_row`'s
return value instead of pre-incrementing, so the budget counts logical
conversations the way PROJECT_ASSIGNED_CLI_LIMIT documents, and seed it from the
assigned rows already inside the window so the bound stays a total rather than
one bound stacked on another. That accounting is behaviour-neutral today (the
assigned query is newest-first, so window rows are always the first rows it
returns) — it makes the documented contract hold by construction rather than by
that ordering accident, and removes an unused return value.
2026-09-24 11:03:46 -03:00
Rodrigo Gomes da Silva 8211acdd84 fix(sessions): give a background row one project chip on every path
`_interactive_row_project_id` resolved state.db assignments for anything that
was not cron or webhook, but the interactive projection loop is also what a
`source_filter='kanban'` scan walks. The same kanban row therefore reported two
different chips depending on the caller: None in the default sidebar (its own
bounded second pass, upstream behaviour) and a resolved project_id under
?source=kanban.

Route every source that owns a bounded project-chip pass back through
`_state_row_project_id`, and name that set once (BACKGROUND_CLI_SOURCES) so the
exclusion list and the chip decision cannot drift apart.
2026-09-24 11:03:46 -03:00
Rodrigo Gomes da Silva bc1ee9d400 fix(sessions): run the assigned recovery pass in the all-profiles view
`?all_profiles=1` passed `project_assigned_limit=False`, on the premise that
its `visible_session_limit=None` first pass already returns every conversation.
It does not: `_load_cli_sessions_uncached` maps `visible_session_limit is None`
to CLI_VISIBLE_SESSION_LIMIT for the interactive pass, so that view truncates
by recency exactly like the single-profile one. Only cron/webhook/kanban limits
reach the reader as `limit=` directly, where None does mean unbounded.

Net effect of the old value: in the "All profiles" sidebar a project-assigned
CLI session older than the newest 20 interactive conversations was missing from
/api/sessions while its project chip still claimed it — the #6659 bug itself,
in the one view that shows every profile at once. Recovery pass 2 (the
unassigned refill) still ran there, so the two passes disagreed as well.

Pass PROJECT_ASSIGNED_CLI_LIMIT instead of None so the per-project bound and
the PROJECT_ASSIGNED_CLI_SCAN_CEILING guard apply per profile, rather than
issuing an unbounded assigned scan for every profile on the box.
2026-09-24 11:03:46 -03:00
Rodrigo Gomes da Silva 7c984f7ab6 test(sessions): pin the per-project budget and the no-project_id schema
Mutation-checked the #6659 suite and found two branches no test held:

* the model recovery pass's per-project accounting. Its query asks for
  PROJECT_ASSIGNED_CLI_LIMIT * len(projects) conversations, so on a
  single-project profile query_limit == the bound and the LIMIT alone
  produces the right answer. Deleting the per-project counter kept all
  21 tests green. Only a skewed multi-project profile (250 in one
  project, 5 in another, scan window 400) exercises it.
* schemas with no project_id column, end to end through the model.
  _state_db_supports_project_ids is asserted directly because dropping
  the probe is behaviour-neutral -- the assigned filter already returns
  nothing for such a schema -- so it is a query-budget guard, and the
  contract worth pinning is the probe's answer plus unchanged output.

Both new tests fail against a tree with the corresponding branch removed.
2026-09-24 11:03:46 -03:00
Rodrigo Gomes da Silva a07ea78414 test(sessions): cover the #6659 review findings and the greptile P1
Each behavioral test fails on the pre-fix tree and passes after:

- 1,000 assigned rows arriving as imported sidecars -> 1,000 kept (finding 1)
- 500 assigned rows over two projects -> per-project bound, so >200 total survive
- 3 assigned + 20 unassigned -> 17 unassigned, at both the route cap and the
  model window, including the logical-conversation count after lineage dedup
  (finding 2)
- a deleted and a cross-profile project_id -> 'project-ghost' surfaced instead of
  None, and an old ghost row resurrected as an unreachable chip-only row
  (finding 3)
- a project assignment on a newer empty continuation -> dropped, both at the
  projection level and end-to-end through the sidebar (finding 4)
- twelve ten-segment chains, limit=10 -> 8 conversations (greptile P1)
- 250 assigned in one project -> the newest 200, oldest dropped, never a newer one
- 'assigned'/'unassigned' partition the conversations exactly, including a
  lineage assigned only on its tip, and on a schema with no project_id column
- master's kanban second pass still projects rows (signature regression guard)
2026-09-24 11:03:46 -03:00
Rodrigo Gomes da Silva ac6b5d0b37 fix(sessions): bound project-assigned CLI sessions per project and budget the unassigned window separately
Letting assigned sessions past the *recent* cap was right; letting them past
*all* bounds was not. Three linked defects from review of #6659:

1. The assigned list was effectively unbounded. models.py's 200 cap only applied
   to state.db's own pass; imported CLI sidecars from all_sessions() never pass
   through it and _cap_recent_cli_sessions() retained every assigned row (1,000
   assigned rows -> 1,000 returned). The FINAL MERGED payload is the only place
   that sees every source of assigned rows, so bound it there - per project, so
   one busy project cannot evict another's history (the >200-assigned case
   greptile flagged), and counting logical conversations, not raw rows.

2. Assigned rows spent the unassigned quota: 3 assigned + 20 unassigned yielded
   17 unassigned, and the model fetched 20 rows total up front so bumping only
   the route counter could not refill. Count the two separately in the route cap
   and add a bounded unassigned refill pass, keyed on the lineage, so 20 unique
   unassigned logical conversations survive sidecar/lineage dedup. The refill
   only runs when the window was actually saturated AND came up short.

3. A deleted or cross-profile project_id hid the session outright: it was
   surfaced unchecked, so the row left "Unassigned" and its overflow became
   default_hidden with no chip left to reveal it. Resolve non-system ids against
   profile-scoped load_projects() and coerce a miss to None BEFORE either cap -
   the mirror of the bug this PR fixes.

read_importable_agent_session_rows() grows a `project_assignment` filter
('assigned'/'unassigned') replacing the require_project_id boolean. Both arms
key on the whole compression lineage and are exact complements, so neither
budget can double-count a lineage.

_state_row_project_id() keeps its (sid, source) signature: master's kanban pass
calls it that way, and giving it a row-dict signature turned that whole pass
into a swallowed TypeError that dropped every kanban row.
2026-09-24 11:03:46 -03:00
Rodrigo Gomes da Silva ff274fc406 perf(sessions): re-widen the raw candidate window when segments consume it
read_importable_agent_session_rows()'s `limit` is a bound on LOGICAL
conversations (the slice runs after lineage projection), but the bounded
candidate CTE spends its raw window on compression segments and rows the
post-projection visibility filters drop. A lineage-heavy profile therefore got
back far fewer conversations than it asked for, and silently: with twelve
ten-segment chains, limit=20 returned 8.

Retry once with a wider window when the first one was fully consumed AND still
under-delivered. The common case is unchanged - the first window is still 8x the
requested limit and the retry never runs when the db simply has nothing more to
give - so the #2628 capped-scan guarantee holds.
2026-09-24 11:03:46 -03:00
Rodrigo Gomes da Silva 9a4e4336ad fix(sessions): keep the lineage project id when the root is the freshest segment
compression_tip() resolves a project assignment across the whole lineage, so it
can find one recorded on a newer EMPTY continuation. When the root is still the
freshest *importable* segment, the `tip is row` early return appended the root
without applying that resolved id and the assignment was silently dropped.

Copy the root and apply lineage_project_id before appending, matching what the
merged-tip path below already does.
2026-09-24 11:03:46 -03:00
Rodrigo Gomes da Silva 9d7374fa25 fix(sessions): keep project-assigned CLI sessions past the recent-session cap
Two independent 20-session caps hid older project-assigned CLI sessions:
models.py only queried the 20 most recent, and routes.py capped the API
payload again. Fixing the query alone was not enough.

- agent_sessions.py: optional project_id filter; propagate the freshest
  project_id along a compression lineage (compression_tip now returns a
  3-tuple) so a Project set only on the tip is not lost
- models.py: second bounded query for older project-assigned sessions;
  skipped when the schema has no project_id; dedupe by id/lineage
- routes.py: keep the 20 recent, drop unassigned overflow, preserve
  assigned overflow as default_hidden (revealed by the project chip)

Adds regression tests for the limit, the root/tip lineage cases and the
route cap.
2026-09-24 11:03:46 -03:00
nesquena-hermes fdf7e5e429 Merge pull request #7807 from nesquena/stage/0924-h
release: #6895 custom-provider model prefix stripped before send
2026-09-24 06:35:56 -07:00
nesquena-hermes 82ee11c71f docs(changelog): #6895 custom-provider model prefix stripped before send 2026-09-24 13:25:26 +00:00
nesquena-hermes 1cb16f6037 Merge PR #6895 into stage/0924-h 2026-09-24 13:25:15 +00:00
nesquena-hermes 0052266c5d Merge pull request #7806 from nesquena/stage/0924-g
release: #6565 gateway reset and tool children stay separate from the parent session
2026-09-24 06:18:24 -07:00
nesquena-hermes 3f76b8a3a9 docs(changelog): #6565 lineage reset/tool children stay separate 2026-09-24 13:04:39 +00:00
nesquena-hermes 30f12dc526 Merge PR #6565 into stage/0924-g 2026-09-24 13:04:21 +00:00
Anthony Ruiz f3545da572 fix(lineage): keep tool children separate across readers 2026-09-24 11:53:47 +00:00
Anthony Ruiz a41bfe8690 fix(lineage): honor _reset_from, fail closed on untrusted markers, hash full watcher payload
Layer three additive pieces on top of the #7021 / #6931 continuation
predicate without changing its shape or CONTINUATION_STARTED_AT_TOLERANCE_SECONDS:

- _reset_from is a boundary marker alongside _branched_from/_delegate_from
  (Hermes Agent stamps it in gateway/session_recovery.py and filters it in
  _NON_CONTINUATION_CHILD_FILTER_SQL). The same predicate drives the sidebar
  listing, lineage metadata/report and the transcript stitcher.
- _branch_markers() returns ('none' | 'markers' | 'unknown', markers);
  unparsable, non-object, too-deep, or non-string marker payloads are
  'unknown' and fail closed as a lineage boundary instead of degrading to
  "no markers" (fail open).
- gateway_watcher._snapshot_hash() hashes the complete canonicalized published
  payload, and the cheap fingerprint covers model_config, so a projection change
  to title/created_at/metadata with fixed (id, updated_at, message_count) emits
  sessions_changed.
2026-09-24 11:53:47 +00:00
webtecnica c784ef7a12 fix(ui): preserve endpoint-style custom slugs host:port in model parsers (#6884) 2026-09-24 08:50:30 -03:00
nesquena-hermes d1b3787573 Merge pull request #7805 from nesquena/stage/0924-f
Release exp-v0.52.371: bound hidden-tab session polling while preserving profile recovery (#7301)
2026-09-24 04:43:48 -07:00
nesquena-hermes 5a2105684d docs(changelog): stamp #7301 under [Unreleased] 2026-09-24 11:27:12 +00:00
nesquena-hermes c2c8ef5fce Merge PR #7301 from laitekin/fix/7299-hidden-session-poll 2026-09-24 11:26:41 +00:00
nesquena-hermes 9bf585ae88 Merge pull request #7804 from nesquena/stage/0924-e
Release exp-v0.52.370: keep image-turn context out of user bubbles; Edit/Undo keeps removed image turns removed (#7754)
2026-09-24 04:20:29 -07:00
nesquena-hermes cb7852cdc1 docs(changelog): stamp #7754 under [Unreleased] 2026-09-24 10:55:11 +00:00
nesquena-hermes 0adc1f6e4f Merge PR #7754 from starship-s/fix/image-turn-display-context 2026-09-24 10:54:56 +00:00
nesquena-hermes 315ea7d3a8 fix(models): let a marked image row consume its replay slot
The row-id fast path for a marked native-image mirror row continued
without advancing state_replay_idx, unlike the ordinary and multimodal-
mirror replay paths. The edited checkpoint then never read as consumed,
so a rerun reply that exists only in state.db was truncated. Master (no
marker branch) keeps that reply.

Advance the replay index when the resolved sidecar row is the next
expected visible row, the same guard the mirror branch uses. Adds a
regression that fails without the fix: sidecar [u1,a1,img#42,a_img,uE],
state [.., marked img#42, a_img, u_old, a_old, uE, aE], watermark 900,
boundary 861 -> aE survives, u_old/a_old stay out.

Found by the release-manager advisor gate on #7754.
2026-09-24 10:47:47 +00:00
nesquena-hermes 0c3550f4a7 Merge pull request #7802 from nesquena/stage/0924-d
Release exp-v0.52.369: scope MCP status, inventory and /reload-mcp to the request profile (#7720)
2026-09-24 03:33:03 -07:00
latekin 08dbd87a36 Merge branch 'master' into fix/7299-hidden-session-poll 2026-09-24 18:20:37 +08:00
nesquena-hermes 026bad541d docs(changelog): stamp #7720 under [Unreleased] 2026-09-24 09:58:07 +00:00
nesquena-hermes 19908b5db4 Merge PR #7720: scope MCP status, inventory and /reload-mcp to the request profile 2026-09-24 09:58:07 +00:00
nesquena-hermes d261041a9a test(mcp): import the fake server class under mcp 1.x and 2.x
mcp 2.x removed mcp.server.fastmcp (the high-level server is now
mcp.server.MCPServer), so the fake stdio server in this test failed to
start on hosts with mcp 2.x installed and four tests failed there. CI pins
mcp<2, so this only affected local runs. Try the 1.x import first and fall
back to the 2.x name.
2026-09-24 09:57:52 +00:00
starship-s 089fb57d25 fix(chat): honor edit watermarks for distinct image rows
Apply the existing truncation and checkpoint-order eligibility to marked
state.db image rows before insertion. Keep removed turns out of full,
limited, and model-context reads without changing no-watermark replay.
Preserve conflicting provider payloads for a uniquely proven retained
image row across Undo without restoring distinct removed rows.

Assisted-by: Hermes Agent:gpt-6-sol
Assisted-by: Codex:gpt-6-luna
2026-09-24 03:04:50 -06:00
nesquena-hermes 05602c96fd Merge pull request #7801 from nesquena/stage/0924-c
Release exp-v0.52.368: phone soft-keyboard Enter inserts a newline (#6746)
2026-09-24 01:27:20 -07:00
nesquena-hermes 3f29a5be08 docs(changelog): stamp #6746 under [Unreleased] 2026-09-24 08:05:40 +00:00
nesquena-hermes 10faa69155 Merge PR #6746 from happy5318/fix/mobile-enter-newline 2026-09-24 08:05:12 +00:00
tancouandClaude Fable 5.1 3b7b5cbe7c fix(mcp): reconcile an emptied profile config on reload; list portable plugin tools
Review gate on #7720 (two regressions reproduced against master with the
installed Agent):

1. Deleting a profile's last MCP server left ADOPTED tools callable after
   /reload-mcp. The scoped teardown only stops connections the profile owns,
   and the Agent's discover_mcp_tools() returns before its reconcile step
   when the profile's config has no server, so an adopted shared connection
   was never judged against the config. /reload-mcp now calls
   register_mcp_servers({}) under the profile's scope before rediscovery:
   the Agent's overlay heal detaches only this profile's overlay for servers
   its config no longer has; the owner's connection keeps running (master's
   wildcard shutdown stopped the owner too).

2. Portable plugin MCP servers are merged by the Agent at runtime and never
   appear in the profile's raw ``mcp_servers``; the registry fallback
   required a config entry and dropped every plugin tool from
   /api/mcp/tools and the Notes inventory. The profile's own registry slot
   (registry_tool_owned_by_view) is the isolation check; the config is not
   an allowlist any more.

Tests (red before, green after): agent-free unit tests for both (the fake
agent now models the overlay heal and discovery's empty-config early
return), and a real-Agent integration test for the adopted-then-deleted
case (owner connection survives, adopter slot emptied, "Removed" reported).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-24 09:53:02 +02:00
tancouandClaude Fable 5.1 dbf0179c53 fix(mcp): report runtime_scope on /api/notes/sources and surface it in the Notes drawer
Review follow-up on #7720 (Greptile P2): the external Notes drawer reads the
same profile-bound MCP inventory as the MCP panel but dropped the
``runtime_scope`` label, so a profile whose runtime scope could not be
confirmed rendered its note sources as inactive/configured with no
explanation. ``/api/notes/sources`` now returns ``runtime_scope`` and the
drawer shows the existing ``mcp_runtime_scope_unavailable`` notice (same key,
no new locale strings) when it is ``unavailable``.

Tests: the skewed-routing unit test now also asserts the notes response
carries ``runtime_scope: "unavailable"`` with its sources inactive.
docs/troubleshooting.md mentions the drawer next to the panel.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-24 09:53:02 +02:00
tancouandClaude Opus 5 90b814f673 fix(mcp): scope MCP status, inventory and /reload-mcp to the request profile
Two profiles configuring a same-named MCP server shared one connection and
the MCP settings panel showed another profile's runtime ("Active, N tools"
next to an empty inventory); /reload-mcp stopped every profile's servers and
rediscovered from the process-default config.

- api.profiles._set_hermes_home() is the single writer of the process-profile
  home: it records get_process_profile_home() and hands it to Hermes Agent's
  hermes_constants.pin_process_hermes_home when available, at startup and on
  switch_profile(process_wide=True), so the per-turn HERMES_HOME mirror no
  longer makes every turn look like the process profile (bare-name,
  cross-profile MCP keys) and the anchor cannot drift from HERMES_HOME.
- Run get_mcp_status/registry reads and /reload-mcp inside
  api.mcp_runtime.mcp_runtime_scope(): request profile's Hermes-home override
  and secret scope (root included), no os.environ mutation, restored on exit.
- One ledger predicate, ledger_key_serves_view() (owned via
  _server_scope_keys/key scope, or adopted via _server_tool_scopes), is shared
  by status, inventory and the reload summary. The Agent's launch-profile view
  is process-wide and name-indexed, so filter_runtime_status_to_view()
  rebuilds connected/tools/status/error from the entries serving the profile:
  the root profile never shows a routed profile's connection (nor lets its own
  parked failed-spawn task borrow a routed profile's live same-named one).
- Inventory lists only tools in the profile's own registry slot for servers it
  configures; responses carry runtime_scope.
- Fail closed when the agent's routing does not match the resolved profile:
  hide runtime data (runtime_scope "unavailable", shown as a notice in the MCP
  panel, 14 locales) and refuse /reload-mcp.
- /reload-mcp shuts down only the profile's own ledger names (parked
  included), drops its own connect cooldown/error entries so a failed spawn is
  retried (the wildcard cleared every cooldown; a scoped shutdown only clears
  the live keys it tears down), rediscovers under the profile's home and
  counts live connections only. The bare wildcard remains for agents without
  profile-scoped MCP.
- Signature introspection is centralized (accepts_keywords) and falls back to
  the bare call instead of blanking every status; ledger key helpers resolve
  to the Agent's tools.mcp_tool_scope when present; tools.mcp_tool is
  resolved by full name so stubbed agents still work.
- ARCHITECTURE.md §4.10 and docs/troubleshooting.md document the boundary.
- Tests: agent-free contract (models adoption, parked tasks, cooldowns and the
  process-wide root view) and real-Agent integration (fake stdio MCP server,
  skips without hermes-agent + mcp SDK).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-24 09:53:02 +02:00
happy5318 bbebe23f7e fix(mobile): preserve tablet hardware-keyboard Enter behavior 2026-09-24 15:26:06 +08:00
nesquena-hermes 2d1e6f812a Merge pull request #7800 from nesquena/stage/0924-b
release: exp-v0.52.367 (#7798 CLI-archived sessions stay archived)
2026-09-24 00:04:26 -07:00
nesquena-hermes 60f61f9038 docs(changelog): stamp #7798 under [Unreleased] 2026-09-24 06:57:37 +00:00
nesquena-hermes 18cf0fa0ba Merge PR #7798 from webtecnica/fix/7548-cli-archived-sessions-projection 2026-09-24 06:57:17 +00:00
nesquena-hermesandwebtecnica 5ecf92382a test(#7548): mark the monkeypatch-target import as intentional (ruff F401)
Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>
2026-09-24 06:56:57 +00:00
nesquena-hermesandwebtecnica afcb1bf420 test(#7548): expect archived=None for a missing sidecar
The sidecar default is now 'no opinion' (None) so the projection can fall
back to state.db's archived flag. Update the #4842 perf test's missing-
sidecar expectation to match.

Co-authored-by: webtecnica <webtecnica@users.noreply.github.com>
2026-09-24 06:45:50 +00:00
nesquena-hermes 5bfe845fbc Merge pull request #7797 from nesquena/stage/0924-a
Release: bounded recovery reads (#7628), prompt notifications (#7493), per-instance settings file (#6433)
2026-09-23 21:31:34 -07:00
webtecnica 98b002d96b fix(sessions): preserve state.db archived status for sessions without sidecars (#7548) 2026-09-24 01:06:03 -03:00
nesquena-hermes 3116280dce docs(changelog): stamp #7628, #7493, #6433 under [Unreleased] 2026-09-24 03:38:33 +00:00